{"action":"created","comment":{"url":"https://api.github.com/repos/ufal/lindat-kontext/pulls/comments/136087192","pull_request_review_id":59576966,"id":136087192,"diff_hunk":"@@ -1,163 +1,242 @@\n-# Copyright (c) 2014 Institute of Formal and Applied Linguistics\n-# Copyright (c) 2016 Charles University in Prague, Faculty of Arts,\n-#                    Institute of the Czech National Corpus\n-# Copyright (c) 2016 Tomas Machalek <tomas.machalek @ gmail.com>\n-#\n-# This program is free software; you can redistribute it and/or\n-# modify it under the terms of the GNU General Public License\n-# as published by the Free Software Foundation; version 2\n-# dated June, 1991.\n-#\n-# This program is distributed in the hope that it will be useful,\n-# but WITHOUT ANY WARRANTY; without even the implied warranty of\n-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n-# GNU General Public License for more details.\n-\n-\"\"\"\n-A custom authentication module for the Institute of Formal and Applied Linguistics.\n-You probably want to implement an authentication solution of your own. Please refer\n-to the documentation or read the dummy_auth.py module to see the required interface.\n+# coding=utf-8\n \"\"\"\n-import crypt\n-import MySQLdb\n+    Authentication and authorization based on Federated login (Shibboleth) and\n+    limited local user support.\n+\n+    This auth is not generic enough to be called ShibbolethAuth because it uses\n+    specific database backed etc.\n \n-from translation import ugettext as _\n+\"\"\"\n+import logging\n+import os\n import plugins\n from plugins.abstract.auth import AbstractSemiInternalAuth\n-from plugins.tree_corparch import CorptreeParser\n-\n+from plugins.abstract import PluginException\n \n-class DbConnection(object):\n+_logger = logging.getLogger(__name__)\n \n-    def __init__(self, conn):\n-        self.conn = conn\n \n-    def get(self):\n-        return self.conn\n+def uni(str_str, encoding=\"utf-8\"):\n+    \"\"\" Try to get unicode without errors \"\"\"\n+    try:\n+        if isinstance(str_str, unicode):\n+            return str_str\n+        elif isinstance(str_str, basestring):\n+            return unicode(str_str, encoding)\n+    except UnicodeError:\n+        pass\n+    try:\n+        return unicode(str(str_str), encoding=encoding, errors='ignore')\n+    except UnicodeError:\n+        pass\n+    return str_str.decode(encoding=encoding, errors=\"ignore\")\n \n \n-def open_db(conf):\n-    conn = MySQLdb.connect(host=conf['lindat:auth_db_host'], user=conf['lindat:auth_db_username'],\n-                           passwd=conf['lindat:auth_db_password'] or \"\", db=conf['lindat:auth_db_name'])\n-    conn.set_character_set('utf8')\n-    return DbConnection(conn)\n-\n-\n-class LINDATAuth(AbstractSemiInternalAuth):\n+class FederatedAuthWithFailover(AbstractSemiInternalAuth):\n     \"\"\"\n-    A custom authentication class for the Institute of the Czech National Corpus\n+        A Shibboleth authentication module with a failover\n+        solution. Please note that the failover solution should be\n+        used only in exceptional cases because no high security\n+        measures have been applied (as opposed to federated login).\n     \"\"\"\n+    ID_KEYS = ('HTTP_EPPN', 'HTTP_PERSISTENT_ID', 'HTTP_MAIL')\n+    RESERVED_USER = '__user_count'\n \n-    MIN_PASSWORD_LENGTH = 5\n+    def get_user_info(self, user_id):\n+        raise NotImplementedError()\n \n-    def __init__(self, db_conn, sessions, corplist, admins, anonymous_id):\n+    def __init__(self, public_corplist, db, sessions, conf, failover):\n         \"\"\"\n-        arguments:\n-        db_conn -- a database connection\n-        sessions -- a session plugin\n-        corplist -- a list of permitted corpora\n-        admins -- list of usernames with administrator privileges\n-        anonymous_id -- an ID of a public/anonymous user\n-        \"\"\"\n-        super(LINDATAuth, self).__init__(anonymous_id=anonymous_id)\n-        self.db_conn = db_conn\n-        self.sessions = sessions\n-        self.corplist = corplist\n-        self.admins = admins\n \n-    def anonymous_user(self):\n-        return dict(id=0, user=None, fullname=_('anonymous'))\n+        Arguments:\n+            public_corplist -- default public corpora list\n+            db_provider -- default database\n+            sessions -- a session plugin\n+\n+        Note:\n+            login_url - used e.g., in dialog ``\n+        \"\"\"\n+        anonymous_id = int(conf['anonymous_user_id'])\n+        super(FederatedAuthWithFailover, self).__init__(anonymous_id=anonymous_id)\n+        self._db = db\n+        self._sessions = sessions\n+        self._public_corplist = public_corplist\n+        self._failover_auth = failover\n+        self._logout_url = conf['logout_url']\n+        self._conf = conf\n \n     def validate_user(self, plugin_api, username, password):\n         \"\"\"\n-        returns:\n-        a dict with user properties or empty dict\n+            Try to find the user using two methods.\n         \"\"\"\n-        getenv = plugin_api.get_environ\n-        if username is not None and username != '':\n-            cols = ('id', 'user', 'pass', 'firstName', 'surname')\n-            cursor = self.db_conn.cursor()\n-            try:\n-                cursor.execute(\"SELECT %s FROM user WHERE user = %%s\" % ','.join(cols), (username, ))\n-                row = cursor.fetchone()\n-                if row and crypt.crypt(password, row[2]) == row[2]:\n-                    row = dict(zip(cols, row))\n-                else:\n-                    row = {}\n-                cursor.close()\n-                if 'id' in row:\n-                    return dict(id=row['id'],\n-                                user=row['user'],\n-                                fullname='%s %s' % (row['firstName'], row['surname']))\n-            except:\n+        if username is not None and 0 < len(username):\n+            if username in FederatedAuthWithFailover.RESERVED_USER:\n+                _logger.warn(\"Reserved username used [%s]!\", username)\n                 return self.anonymous_user()\n+            user_d = self._failover_auth.auth(self._db, username, password)\n         else:\n-            username = getenv('HTTP_EPPN') or getenv('HTTP_PERSISTENT_ID') or getenv('HTTP_MAIL')\n-            if username is None or username == '':\n-                return self.anonymous_user()\n-            first_name = getenv('HTTP_GIVENNAME')\n-            surname = getenv('HTTP_SN')\n-            if not first_name and not surname:\n-                full_name = getenv('HTTP_DISPLAYNAME') or getenv('HTTP_CN')\n-                first_name, surname = self.parse_full_name(full_name)\n-            cols = ('id', 'user', 'pass', 'firstName', 'surname')\n-            cursor = self.db_conn.cursor()\n-            cursor.execute(\"SELECT %s FROM user WHERE user = %%s\" % ','.join(cols), (username, ))\n-            row = cursor.fetchone()\n-            if not row:\n-                cursor.execute(\"INSERT INTO user (user, firstName, surname) VALUES (%s, %s, %s)\",\n-                               (username, first_name, surname))\n-            else:\n-                cursor.execute(\"UPDATE user SET firstName=%s, surname=%s WHERE user=%s\",\n-                               (first_name, surname, username))\n-            cursor.execute(\"SELECT %s FROM user WHERE user = %%s\" % ','.join(cols),\n-                           (username, ))\n-            row = cursor.fetchone()\n-            row = dict(zip(cols, row)) if row else {}\n-            cursor.close()\n-            if 'id' in row:\n-                return dict(id=row['id'],\n-                            user=row['user'],\n-                            fullname='%s %s' % (row['firstName'], row['surname']))\n+            user_d = self._auth(plugin_api)\n+\n+        if user_d is not None:\n+            user_id = int(user_d[\"id\"])\n+            return {\n+                'id': user_id,\n+                'user': user_d.get(\"username\", \"unknown\"),\n+                'fullname': user_d.get(\"fullname\", \"Mr. No Name\")\n+            }\n+\n         return self.anonymous_user()\n \n+    def get_logout_url(self, return_url=None):\n+        return self._logout_url\n+\n     def logout(self, session):\n-        self.sessions.delete(session)\n+        self._sessions.delete(session)\n         session.clear()\n \n     def permitted_corpora(self, user_id):\n-        return self.corplist\n+        # TODO(jm) based on user_id\n+        return self._public_corplist\n \n     def is_administrator(self, user_id):\n-        \"\"\"\n-        Tests whether the current user's name belongs to the 'administrators' group\n-        \"\"\"\n-        return user_id in self.admins\n+        # TODO(jm)\n+        return False\n \n     def logout_hook(self, plugin_api):\n         plugin_api.redirect('%sfirst_form' % (plugin_api.root_url,))\n \n-    def get_restricted_corp_variant(self, corpus_name):\n-        return corpus_name\n+    def _new_user_id(self):\n+        return self._db.incr(FederatedAuthWithFailover.RESERVED_USER)","path":"lib/plugins/lindat_auth/__init__.py","position":217,"original_position":217,"commit_id":"364403a658ca2e1489e6115398682e24ee560b9d","original_commit_id":"364403a658ca2e1489e6115398682e24ee560b9d","user":{"login":"vidiecan","id":332350,"avatar_url":"https://avatars2.githubusercontent.com/u/332350?v=4","gravatar_id":"","url":"https://api.github.com/users/vidiecan","html_url":"https://github.com/vidiecan","followers_url":"https://api.github.com/users/vidiecan/followers","following_url":"https://api.github.com/users/vidiecan/following{/other_user}","gists_url":"https://api.github.com/users/vidiecan/gists{/gist_id}","starred_url":"https://api.github.com/users/vidiecan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vidiecan/subscriptions","organizations_url":"https://api.github.com/users/vidiecan/orgs","repos_url":"https://api.github.com/users/vidiecan/repos","events_url":"https://api.github.com/users/vidiecan/events{/privacy}","received_events_url":"https://api.github.com/users/vidiecan/received_events","type":"User","site_admin":false},"body":"no it is a number  (in redis a string anyway)","created_at":"2017-08-30T14:37:48Z","updated_at":"2017-08-30T14:37:48Z","html_url":"https://github.com/ufal/lindat-kontext/pull/72#discussion_r136087192","pull_request_url":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/ufal/lindat-kontext/pulls/comments/136087192"},"html":{"href":"https://github.com/ufal/lindat-kontext/pull/72#discussion_r136087192"},"pull_request":{"href":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72"}},"in_reply_to_id":136069018},"pull_request":{"url":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72","id":137711067,"html_url":"https://github.com/ufal/lindat-kontext/pull/72","diff_url":"https://github.com/ufal/lindat-kontext/pull/72.diff","patch_url":"https://github.com/ufal/lindat-kontext/pull/72.patch","issue_url":"https://api.github.com/repos/ufal/lindat-kontext/issues/72","number":72,"state":"open","locked":false,"title":"Fixes lindat_auth plugin - Issue 57","user":{"login":"vidiecan","id":332350,"avatar_url":"https://avatars2.githubusercontent.com/u/332350?v=4","gravatar_id":"","url":"https://api.github.com/users/vidiecan","html_url":"https://github.com/vidiecan","followers_url":"https://api.github.com/users/vidiecan/followers","following_url":"https://api.github.com/users/vidiecan/following{/other_user}","gists_url":"https://api.github.com/users/vidiecan/gists{/gist_id}","starred_url":"https://api.github.com/users/vidiecan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/vidiecan/subscriptions","organizations_url":"https://api.github.com/users/vidiecan/orgs","repos_url":"https://api.github.com/users/vidiecan/repos","events_url":"https://api.github.com/users/vidiecan/events{/privacy}","received_events_url":"https://api.github.com/users/vidiecan/received_events","type":"User","site_admin":false},"body":"- all user auth info stored in redis (different shard though)\r\n- removes dependency on mysql\r\n- cookie path prefix added because of our double proxy\r\n- minor changes to better support multiple instances installation\r\n- added HTTP_X_FORWARDED_PROTOCOL to checked env\r\n- pm2 deploy for jm added\r\n- added required API to redis db abstraction\r\n- PEP8 applied to changed filed\r\n- auth/aai_config slightly less dependent on lindat theme","created_at":"2017-08-25T16:27:39Z","updated_at":"2017-08-30T14:37:48Z","closed_at":null,"merged_at":null,"merge_commit_sha":"38defbd3b9fc23418f89534a29e135f9e1976212","assignee":null,"assignees":[],"requested_reviewers":[],"milestone":null,"commits_url":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72/commits","review_comments_url":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72/comments","review_comment_url":"https://api.github.com/repos/ufal/lindat-kontext/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ufal/lindat-kontext/issues/72/comments","statuses_url":"https://api.github.com/repos/ufal/lindat-kontext/statuses/364403a658ca2e1489e6115398682e24ee560b9d","head":{"label":"ufal:issue_57","ref":"issue_57","sha":"364403a658ca2e1489e6115398682e24ee560b9d","user":{"login":"ufal","id":5220747,"avatar_url":"https://avatars2.githubusercontent.com/u/5220747?v=4","gravatar_id":"","url":"https://api.github.com/users/ufal","html_url":"https://github.com/ufal","followers_url":"https://api.github.com/users/ufal/followers","following_url":"https://api.github.com/users/ufal/following{/other_user}","gists_url":"https://api.github.com/users/ufal/gists{/gist_id}","starred_url":"https://api.github.com/users/ufal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ufal/subscriptions","organizations_url":"https://api.github.com/users/ufal/orgs","repos_url":"https://api.github.com/users/ufal/repos","events_url":"https://api.github.com/users/ufal/events{/privacy}","received_events_url":"https://api.github.com/users/ufal/received_events","type":"Organization","site_admin":false},"repo":{"id":36013094,"name":"lindat-kontext","full_name":"ufal/lindat-kontext","owner":{"login":"ufal","id":5220747,"avatar_url":"https://avatars2.githubusercontent.com/u/5220747?v=4","gravatar_id":"","url":"https://api.github.com/users/ufal","html_url":"https://github.com/ufal","followers_url":"https://api.github.com/users/ufal/followers","following_url":"https://api.github.com/users/ufal/following{/other_user}","gists_url":"https://api.github.com/users/ufal/gists{/gist_id}","starred_url":"https://api.github.com/users/ufal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ufal/subscriptions","organizations_url":"https://api.github.com/users/ufal/orgs","repos_url":"https://api.github.com/users/ufal/repos","events_url":"https://api.github.com/users/ufal/events{/privacy}","received_events_url":"https://api.github.com/users/ufal/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ufal/lindat-kontext","description":"An alternative web front-end for the Manatee corpus search engine","fork":true,"url":"https://api.github.com/repos/ufal/lindat-kontext","forks_url":"https://api.github.com/repos/ufal/lindat-kontext/forks","keys_url":"https://api.github.com/repos/ufal/lindat-kontext/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ufal/lindat-kontext/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ufal/lindat-kontext/teams","hooks_url":"https://api.github.com/repos/ufal/lindat-kontext/hooks","issue_events_url":"https://api.github.com/repos/ufal/lindat-kontext/issues/events{/number}","events_url":"https://api.github.com/repos/ufal/lindat-kontext/events","assignees_url":"https://api.github.com/repos/ufal/lindat-kontext/assignees{/user}","branches_url":"https://api.github.com/repos/ufal/lindat-kontext/branches{/branch}","tags_url":"https://api.github.com/repos/ufal/lindat-kontext/tags","blobs_url":"https://api.github.com/repos/ufal/lindat-kontext/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ufal/lindat-kontext/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ufal/lindat-kontext/git/refs{/sha}","trees_url":"https://api.github.com/repos/ufal/lindat-kontext/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ufal/lindat-kontext/statuses/{sha}","languages_url":"https://api.github.com/repos/ufal/lindat-kontext/languages","stargazers_url":"https://api.github.com/repos/ufal/lindat-kontext/stargazers","contributors_url":"https://api.github.com/repos/ufal/lindat-kontext/contributors","subscribers_url":"https://api.github.com/repos/ufal/lindat-kontext/subscribers","subscription_url":"https://api.github.com/repos/ufal/lindat-kontext/subscription","commits_url":"https://api.github.com/repos/ufal/lindat-kontext/commits{/sha}","git_commits_url":"https://api.github.com/repos/ufal/lindat-kontext/git/commits{/sha}","comments_url":"https://api.github.com/repos/ufal/lindat-kontext/comments{/number}","issue_comment_url":"https://api.github.com/repos/ufal/lindat-kontext/issues/comments{/number}","contents_url":"https://api.github.com/repos/ufal/lindat-kontext/contents/{+path}","compare_url":"https://api.github.com/repos/ufal/lindat-kontext/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ufal/lindat-kontext/merges","archive_url":"https://api.github.com/repos/ufal/lindat-kontext/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ufal/lindat-kontext/downloads","issues_url":"https://api.github.com/repos/ufal/lindat-kontext/issues{/number}","pulls_url":"https://api.github.com/repos/ufal/lindat-kontext/pulls{/number}","milestones_url":"https://api.github.com/repos/ufal/lindat-kontext/milestones{/number}","notifications_url":"https://api.github.com/repos/ufal/lindat-kontext/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ufal/lindat-kontext/labels{/name}","releases_url":"https://api.github.com/repos/ufal/lindat-kontext/releases{/id}","deployments_url":"https://api.github.com/repos/ufal/lindat-kontext/deployments","created_at":"2015-05-21T13:03:35Z","updated_at":"2017-07-31T18:59:45Z","pushed_at":"2017-08-30T12:58:46Z","git_url":"git://github.com/ufal/lindat-kontext.git","ssh_url":"git@github.com:ufal/lindat-kontext.git","clone_url":"https://github.com/ufal/lindat-kontext.git","svn_url":"https://github.com/ufal/lindat-kontext","homepage":null,"size":13966,"stargazers_count":2,"watchers_count":2,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":30,"forks":0,"open_issues":30,"watchers":2,"default_branch":"release-0.5.x"}},"base":{"label":"ufal:kontext-dev","ref":"kontext-dev","sha":"0c97500994c65497d9ac5b8a41cb030e6ac36665","user":{"login":"ufal","id":5220747,"avatar_url":"https://avatars2.githubusercontent.com/u/5220747?v=4","gravatar_id":"","url":"https://api.github.com/users/ufal","html_url":"https://github.com/ufal","followers_url":"https://api.github.com/users/ufal/followers","following_url":"https://api.github.com/users/ufal/following{/other_user}","gists_url":"https://api.github.com/users/ufal/gists{/gist_id}","starred_url":"https://api.github.com/users/ufal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ufal/subscriptions","organizations_url":"https://api.github.com/users/ufal/orgs","repos_url":"https://api.github.com/users/ufal/repos","events_url":"https://api.github.com/users/ufal/events{/privacy}","received_events_url":"https://api.github.com/users/ufal/received_events","type":"Organization","site_admin":false},"repo":{"id":36013094,"name":"lindat-kontext","full_name":"ufal/lindat-kontext","owner":{"login":"ufal","id":5220747,"avatar_url":"https://avatars2.githubusercontent.com/u/5220747?v=4","gravatar_id":"","url":"https://api.github.com/users/ufal","html_url":"https://github.com/ufal","followers_url":"https://api.github.com/users/ufal/followers","following_url":"https://api.github.com/users/ufal/following{/other_user}","gists_url":"https://api.github.com/users/ufal/gists{/gist_id}","starred_url":"https://api.github.com/users/ufal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ufal/subscriptions","organizations_url":"https://api.github.com/users/ufal/orgs","repos_url":"https://api.github.com/users/ufal/repos","events_url":"https://api.github.com/users/ufal/events{/privacy}","received_events_url":"https://api.github.com/users/ufal/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ufal/lindat-kontext","description":"An alternative web front-end for the Manatee corpus search engine","fork":true,"url":"https://api.github.com/repos/ufal/lindat-kontext","forks_url":"https://api.github.com/repos/ufal/lindat-kontext/forks","keys_url":"https://api.github.com/repos/ufal/lindat-kontext/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ufal/lindat-kontext/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ufal/lindat-kontext/teams","hooks_url":"https://api.github.com/repos/ufal/lindat-kontext/hooks","issue_events_url":"https://api.github.com/repos/ufal/lindat-kontext/issues/events{/number}","events_url":"https://api.github.com/repos/ufal/lindat-kontext/events","assignees_url":"https://api.github.com/repos/ufal/lindat-kontext/assignees{/user}","branches_url":"https://api.github.com/repos/ufal/lindat-kontext/branches{/branch}","tags_url":"https://api.github.com/repos/ufal/lindat-kontext/tags","blobs_url":"https://api.github.com/repos/ufal/lindat-kontext/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ufal/lindat-kontext/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ufal/lindat-kontext/git/refs{/sha}","trees_url":"https://api.github.com/repos/ufal/lindat-kontext/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ufal/lindat-kontext/statuses/{sha}","languages_url":"https://api.github.com/repos/ufal/lindat-kontext/languages","stargazers_url":"https://api.github.com/repos/ufal/lindat-kontext/stargazers","contributors_url":"https://api.github.com/repos/ufal/lindat-kontext/contributors","subscribers_url":"https://api.github.com/repos/ufal/lindat-kontext/subscribers","subscription_url":"https://api.github.com/repos/ufal/lindat-kontext/subscription","commits_url":"https://api.github.com/repos/ufal/lindat-kontext/commits{/sha}","git_commits_url":"https://api.github.com/repos/ufal/lindat-kontext/git/commits{/sha}","comments_url":"https://api.github.com/repos/ufal/lindat-kontext/comments{/number}","issue_comment_url":"https://api.github.com/repos/ufal/lindat-kontext/issues/comments{/number}","contents_url":"https://api.github.com/repos/ufal/lindat-kontext/contents/{+path}","compare_url":"https://api.github.com/repos/ufal/lindat-kontext/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ufal/lindat-kontext/merges","archive_url":"https://api.github.com/repos/ufal/lindat-kontext/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ufal/lindat-kontext/downloads","issues_url":"https://api.github.com/repos/ufal/lindat-kontext/issues{/number}","pulls_url":"https://api.github.com/repos/ufal/lindat-kontext/pulls{/number}","milestones_url":"https://api.github.com/repos/ufal/lindat-kontext/milestones{/number}","notifications_url":"https://api.github.com/repos/ufal/lindat-kontext/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ufal/lindat-kontext/labels{/name}","releases_url":"https://api.github.com/repos/ufal/lindat-kontext/releases{/id}","deployments_url":"https://api.github.com/repos/ufal/lindat-kontext/deployments","created_at":"2015-05-21T13:03:35Z","updated_at":"2017-07-31T18:59:45Z","pushed_at":"2017-08-30T12:58:46Z","git_url":"git://github.com/ufal/lindat-kontext.git","ssh_url":"git@github.com:ufal/lindat-kontext.git","clone_url":"https://github.com/ufal/lindat-kontext.git","svn_url":"https://github.com/ufal/lindat-kontext","homepage":null,"size":13966,"stargazers_count":2,"watchers_count":2,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":30,"forks":0,"open_issues":30,"watchers":2,"default_branch":"release-0.5.x"}},"_links":{"self":{"href":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72"},"html":{"href":"https://github.com/ufal/lindat-kontext/pull/72"},"issue":{"href":"https://api.github.com/repos/ufal/lindat-kontext/issues/72"},"comments":{"href":"https://api.github.com/repos/ufal/lindat-kontext/issues/72/comments"},"review_comments":{"href":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72/comments"},"review_comment":{"href":"https://api.github.com/repos/ufal/lindat-kontext/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ufal/lindat-kontext/pulls/72/commits"},"statuses":{"href":"https://api.github.com/repos/ufal/lindat-kontext/statuses/364403a658ca2e1489e6115398682e24ee560b9d"}},"author_association":"NONE"}}
{"actor":{"display_login":"vidiecan"}}