{"action":"created","comment":{"url":"https://api.github.com/repos/freeipa/freeipa/pulls/comments/212599682","pull_request_review_id":149265482,"id":212599682,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxMjU5OTY4Mg==","diff_hunk":"@@ -506,6 +508,95 @@ def save(self, dest):\n         self.__doc.write(dest)\n \n \n+class CSVToken(object):\n+    @property\n+    def id(self):\n+        return self.__id\n+\n+    @property\n+    def options(self):\n+        return self.__options\n+\n+    @property\n+    def removed(self):\n+        return self.__removed\n+\n+    def __init__(self, id, options):\n+        self.__id = id\n+        self.__options = options\n+        self.__removed = False\n+\n+    def remove(self):\n+        self.__removed = True\n+\n+\n+class CSVDocument(object):\n+    _FIELDS = (\"serial\", \"key\", \"type\", \"digits\", \"interval\")\n+\n+    _MAP = (\n+        ('type', 'type', lambda v, o: v.strip()),\n+        ('serial', 'ipatokenserial', lambda v, o: v.strip()),\n+        (\n+            'key',\n+            'ipatokenotpkey',\n+            lambda v, o: base64.b32encode(base64.b64decode(v)).decode('ascii')\n+        ),\n+        ('digits', 'ipatokenotpdigits', lambda v, o: v),\n+        ('interval', 'ipatokentotptimestep', lambda v, o: v),\n+    )\n+\n+    @property\n+    def keyname(self):\n+        # Always return None as we have no key names in CSV format\n+        return None\n+\n+    def __init__(self, filename):\n+        self.__keypackages = []\n+\n+        with open(filename, \"r\") as f:\n+            reader = csv.reader(f)\n+            for row in reader:\n+                if len(row) != len(self._FIELDS):\n+                    raise ValidationError(\n+                        'Provided token has incorrect number of fields')\n+                try:\n+                    vals = dict(zip(self._FIELDS, row))\n+\n+                    # Copy values into output.\n+                    options = {}\n+                    for (dk, ok, f) in self._MAP:\n+                        if dk in vals:\n+                            options[ok] = f(vals[dk], options)\n+\n+                    self.__keypackages.append(\n+                        CSVToken(options['ipatokenserial'], options))\n+                except csv.Error as e:\n+                    raise ValidationError(\n+                        'Parsing CSV failed: {e}'.format(e=str(e)))\n+                except (TypeError, binascii.Error) as e:\n+                    raise ValidationError(\n+                        'Provided token key is not properly encoded: {e}'.\n+                        format(e=str(e)))\n+\n+    def getKeyPackages(self):\n+        for kp in self.__keypackages:\n+            yield kp\n+\n+    def save(self, dest):\n+        with open(dest, \"w\") as f:\n+            writer = csv.writer(f)","path":"ipaserver/install/ipa_otptoken_import.py","position":96,"original_position":96,"commit_id":"b4a3a06f5ed481bf3f00146bd8a40f2a5b1ec747","original_commit_id":"b4a3a06f5ed481bf3f00146bd8a40f2a5b1ec747","user":{"login":"tiran","id":444071,"node_id":"MDQ6VXNlcjQ0NDA3MQ==","avatar_url":"https://avatars3.githubusercontent.com/u/444071?v=4","gravatar_id":"","url":"https://api.github.com/users/tiran","html_url":"https://github.com/tiran","followers_url":"https://api.github.com/users/tiran/followers","following_url":"https://api.github.com/users/tiran/following{/other_user}","gists_url":"https://api.github.com/users/tiran/gists{/gist_id}","starred_url":"https://api.github.com/users/tiran/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tiran/subscriptions","organizations_url":"https://api.github.com/users/tiran/orgs","repos_url":"https://api.github.com/users/tiran/repos","events_url":"https://api.github.com/users/tiran/events{/privacy}","received_events_url":"https://api.github.com/users/tiran/received_events","type":"User","site_admin":false},"body":"A DictWriter also simplified this code a lot.","created_at":"2018-08-24T11:33:09Z","updated_at":"2018-08-24T11:42:21Z","html_url":"https://github.com/freeipa/freeipa/pull/2241#discussion_r212599682","pull_request_url":"https://api.github.com/repos/freeipa/freeipa/pulls/2241","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/freeipa/freeipa/pulls/comments/212599682"},"html":{"href":"https://github.com/freeipa/freeipa/pull/2241#discussion_r212599682"},"pull_request":{"href":"https://api.github.com/repos/freeipa/freeipa/pulls/2241"}}},"pull_request":{"url":"https://api.github.com/repos/freeipa/freeipa/pulls/2241","id":207763541,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA3NzYzNTQx","html_url":"https://github.com/freeipa/freeipa/pull/2241","diff_url":"https://github.com/freeipa/freeipa/pull/2241.diff","patch_url":"https://github.com/freeipa/freeipa/pull/2241.patch","issue_url":"https://api.github.com/repos/freeipa/freeipa/issues/2241","number":2241,"state":"open","locked":false,"title":"ipa-otptoken-import: support import from CSV file","user":{"login":"abbra","id":2066,"node_id":"MDQ6VXNlcjIwNjY=","avatar_url":"https://avatars1.githubusercontent.com/u/2066?v=4","gravatar_id":"","url":"https://api.github.com/users/abbra","html_url":"https://github.com/abbra","followers_url":"https://api.github.com/users/abbra/followers","following_url":"https://api.github.com/users/abbra/following{/other_user}","gists_url":"https://api.github.com/users/abbra/gists{/gist_id}","starred_url":"https://api.github.com/users/abbra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abbra/subscriptions","organizations_url":"https://api.github.com/users/abbra/orgs","repos_url":"https://api.github.com/users/abbra/repos","events_url":"https://api.github.com/users/abbra/events{/privacy}","received_events_url":"https://api.github.com/users/abbra/received_events","type":"User","site_admin":false},"body":"Allow import from a CSV file where fields have a fixed meaning:\r\n\r\n\"serial\", \"key\", \"type\", \"digits\", \"interval\"\r\n\r\nFixes: https://pagure.io/freeipa/issue/7673\r\n\r\nPair-programmed-with: Nathaniel McCallum <npmccallum@redhat.com>\r\nSigned-off-by: Alexander Bokovoy <abokovoy@redhat.com>","created_at":"2018-08-11T07:55:22Z","updated_at":"2018-08-24T11:42:21Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7c85df1b8627df20bfb061841490b7f4a23dfa95","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":990694887,"node_id":"MDU6TGFiZWw5OTA2OTQ4ODc=","url":"https://api.github.com/repos/freeipa/freeipa/labels/ipa-4-6","name":"ipa-4-6","color":"fc8523","default":false},{"id":969297832,"node_id":"MDU6TGFiZWw5NjkyOTc4MzI=","url":"https://api.github.com/repos/freeipa/freeipa/labels/ipa-4-7","name":"ipa-4-7","color":"c8f438","default":false},{"id":775385773,"node_id":"MDU6TGFiZWw3NzUzODU3NzM=","url":"https://api.github.com/repos/freeipa/freeipa/labels/needs%20review","name":"needs review","color":"e4c2fc","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/freeipa/freeipa/pulls/2241/commits","review_comments_url":"https://api.github.com/repos/freeipa/freeipa/pulls/2241/comments","review_comment_url":"https://api.github.com/repos/freeipa/freeipa/pulls/comments{/number}","comments_url":"https://api.github.com/repos/freeipa/freeipa/issues/2241/comments","statuses_url":"https://api.github.com/repos/freeipa/freeipa/statuses/b4a3a06f5ed481bf3f00146bd8a40f2a5b1ec747","head":{"label":"abbra:tokens-csv","ref":"tokens-csv","sha":"b4a3a06f5ed481bf3f00146bd8a40f2a5b1ec747","user":{"login":"abbra","id":2066,"node_id":"MDQ6VXNlcjIwNjY=","avatar_url":"https://avatars1.githubusercontent.com/u/2066?v=4","gravatar_id":"","url":"https://api.github.com/users/abbra","html_url":"https://github.com/abbra","followers_url":"https://api.github.com/users/abbra/followers","following_url":"https://api.github.com/users/abbra/following{/other_user}","gists_url":"https://api.github.com/users/abbra/gists{/gist_id}","starred_url":"https://api.github.com/users/abbra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abbra/subscriptions","organizations_url":"https://api.github.com/users/abbra/orgs","repos_url":"https://api.github.com/users/abbra/repos","events_url":"https://api.github.com/users/abbra/events{/privacy}","received_events_url":"https://api.github.com/users/abbra/received_events","type":"User","site_admin":false},"repo":{"id":75370066,"node_id":"MDEwOlJlcG9zaXRvcnk3NTM3MDA2Ng==","name":"freeipa","full_name":"abbra/freeipa","owner":{"login":"abbra","id":2066,"node_id":"MDQ6VXNlcjIwNjY=","avatar_url":"https://avatars1.githubusercontent.com/u/2066?v=4","gravatar_id":"","url":"https://api.github.com/users/abbra","html_url":"https://github.com/abbra","followers_url":"https://api.github.com/users/abbra/followers","following_url":"https://api.github.com/users/abbra/following{/other_user}","gists_url":"https://api.github.com/users/abbra/gists{/gist_id}","starred_url":"https://api.github.com/users/abbra/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/abbra/subscriptions","organizations_url":"https://api.github.com/users/abbra/orgs","repos_url":"https://api.github.com/users/abbra/repos","events_url":"https://api.github.com/users/abbra/events{/privacy}","received_events_url":"https://api.github.com/users/abbra/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/abbra/freeipa","description":"Mirror of FreeIPA, an integrated security information management solution","fork":true,"url":"https://api.github.com/repos/abbra/freeipa","forks_url":"https://api.github.com/repos/abbra/freeipa/forks","keys_url":"https://api.github.com/repos/abbra/freeipa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/abbra/freeipa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/abbra/freeipa/teams","hooks_url":"https://api.github.com/repos/abbra/freeipa/hooks","issue_events_url":"https://api.github.com/repos/abbra/freeipa/issues/events{/number}","events_url":"https://api.github.com/repos/abbra/freeipa/events","assignees_url":"https://api.github.com/repos/abbra/freeipa/assignees{/user}","branches_url":"https://api.github.com/repos/abbra/freeipa/branches{/branch}","tags_url":"https://api.github.com/repos/abbra/freeipa/tags","blobs_url":"https://api.github.com/repos/abbra/freeipa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/abbra/freeipa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/abbra/freeipa/git/refs{/sha}","trees_url":"https://api.github.com/repos/abbra/freeipa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/abbra/freeipa/statuses/{sha}","languages_url":"https://api.github.com/repos/abbra/freeipa/languages","stargazers_url":"https://api.github.com/repos/abbra/freeipa/stargazers","contributors_url":"https://api.github.com/repos/abbra/freeipa/contributors","subscribers_url":"https://api.github.com/repos/abbra/freeipa/subscribers","subscription_url":"https://api.github.com/repos/abbra/freeipa/subscription","commits_url":"https://api.github.com/repos/abbra/freeipa/commits{/sha}","git_commits_url":"https://api.github.com/repos/abbra/freeipa/git/commits{/sha}","comments_url":"https://api.github.com/repos/abbra/freeipa/comments{/number}","issue_comment_url":"https://api.github.com/repos/abbra/freeipa/issues/comments{/number}","contents_url":"https://api.github.com/repos/abbra/freeipa/contents/{+path}","compare_url":"https://api.github.com/repos/abbra/freeipa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/abbra/freeipa/merges","archive_url":"https://api.github.com/repos/abbra/freeipa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/abbra/freeipa/downloads","issues_url":"https://api.github.com/repos/abbra/freeipa/issues{/number}","pulls_url":"https://api.github.com/repos/abbra/freeipa/pulls{/number}","milestones_url":"https://api.github.com/repos/abbra/freeipa/milestones{/number}","notifications_url":"https://api.github.com/repos/abbra/freeipa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/abbra/freeipa/labels{/name}","releases_url":"https://api.github.com/repos/abbra/freeipa/releases{/id}","deployments_url":"https://api.github.com/repos/abbra/freeipa/deployments","created_at":"2016-12-02T07:03:04Z","updated_at":"2018-08-14T13:36:37Z","pushed_at":"2018-08-24T09:09:53Z","git_url":"git://github.com/abbra/freeipa.git","ssh_url":"git@github.com:abbra/freeipa.git","clone_url":"https://github.com/abbra/freeipa.git","svn_url":"https://github.com/abbra/freeipa","homepage":"http://www.freeipa.org","size":50452,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":6,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":6,"watchers":0,"default_branch":"master"}},"base":{"label":"freeipa:master","ref":"master","sha":"c8da61b92ac08ab206df260765c531e70d0342f5","user":{"login":"freeipa","id":10979201,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwOTc5MjAx","avatar_url":"https://avatars2.githubusercontent.com/u/10979201?v=4","gravatar_id":"","url":"https://api.github.com/users/freeipa","html_url":"https://github.com/freeipa","followers_url":"https://api.github.com/users/freeipa/followers","following_url":"https://api.github.com/users/freeipa/following{/other_user}","gists_url":"https://api.github.com/users/freeipa/gists{/gist_id}","starred_url":"https://api.github.com/users/freeipa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/freeipa/subscriptions","organizations_url":"https://api.github.com/users/freeipa/orgs","repos_url":"https://api.github.com/users/freeipa/repos","events_url":"https://api.github.com/users/freeipa/events{/privacy}","received_events_url":"https://api.github.com/users/freeipa/received_events","type":"Organization","site_admin":false},"repo":{"id":4340318,"node_id":"MDEwOlJlcG9zaXRvcnk0MzQwMzE4","name":"freeipa","full_name":"freeipa/freeipa","owner":{"login":"freeipa","id":10979201,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwOTc5MjAx","avatar_url":"https://avatars2.githubusercontent.com/u/10979201?v=4","gravatar_id":"","url":"https://api.github.com/users/freeipa","html_url":"https://github.com/freeipa","followers_url":"https://api.github.com/users/freeipa/followers","following_url":"https://api.github.com/users/freeipa/following{/other_user}","gists_url":"https://api.github.com/users/freeipa/gists{/gist_id}","starred_url":"https://api.github.com/users/freeipa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/freeipa/subscriptions","organizations_url":"https://api.github.com/users/freeipa/orgs","repos_url":"https://api.github.com/users/freeipa/repos","events_url":"https://api.github.com/users/freeipa/events{/privacy}","received_events_url":"https://api.github.com/users/freeipa/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/freeipa/freeipa","description":"Mirror of FreeIPA, an integrated security information management solution","fork":false,"url":"https://api.github.com/repos/freeipa/freeipa","forks_url":"https://api.github.com/repos/freeipa/freeipa/forks","keys_url":"https://api.github.com/repos/freeipa/freeipa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/freeipa/freeipa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/freeipa/freeipa/teams","hooks_url":"https://api.github.com/repos/freeipa/freeipa/hooks","issue_events_url":"https://api.github.com/repos/freeipa/freeipa/issues/events{/number}","events_url":"https://api.github.com/repos/freeipa/freeipa/events","assignees_url":"https://api.github.com/repos/freeipa/freeipa/assignees{/user}","branches_url":"https://api.github.com/repos/freeipa/freeipa/branches{/branch}","tags_url":"https://api.github.com/repos/freeipa/freeipa/tags","blobs_url":"https://api.github.com/repos/freeipa/freeipa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/freeipa/freeipa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/freeipa/freeipa/git/refs{/sha}","trees_url":"https://api.github.com/repos/freeipa/freeipa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/freeipa/freeipa/statuses/{sha}","languages_url":"https://api.github.com/repos/freeipa/freeipa/languages","stargazers_url":"https://api.github.com/repos/freeipa/freeipa/stargazers","contributors_url":"https://api.github.com/repos/freeipa/freeipa/contributors","subscribers_url":"https://api.github.com/repos/freeipa/freeipa/subscribers","subscription_url":"https://api.github.com/repos/freeipa/freeipa/subscription","commits_url":"https://api.github.com/repos/freeipa/freeipa/commits{/sha}","git_commits_url":"https://api.github.com/repos/freeipa/freeipa/git/commits{/sha}","comments_url":"https://api.github.com/repos/freeipa/freeipa/comments{/number}","issue_comment_url":"https://api.github.com/repos/freeipa/freeipa/issues/comments{/number}","contents_url":"https://api.github.com/repos/freeipa/freeipa/contents/{+path}","compare_url":"https://api.github.com/repos/freeipa/freeipa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/freeipa/freeipa/merges","archive_url":"https://api.github.com/repos/freeipa/freeipa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/freeipa/freeipa/downloads","issues_url":"https://api.github.com/repos/freeipa/freeipa/issues{/number}","pulls_url":"https://api.github.com/repos/freeipa/freeipa/pulls{/number}","milestones_url":"https://api.github.com/repos/freeipa/freeipa/milestones{/number}","notifications_url":"https://api.github.com/repos/freeipa/freeipa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/freeipa/freeipa/labels{/name}","releases_url":"https://api.github.com/repos/freeipa/freeipa/releases{/id}","deployments_url":"https://api.github.com/repos/freeipa/freeipa/deployments","created_at":"2012-05-15T20:34:40Z","updated_at":"2018-08-23T12:49:54Z","pushed_at":"2018-08-24T10:56:32Z","git_url":"git://github.com/freeipa/freeipa.git","ssh_url":"git@github.com:freeipa/freeipa.git","clone_url":"https://github.com/freeipa/freeipa.git","svn_url":"https://github.com/freeipa/freeipa","homepage":"http://www.freeipa.org","size":50883,"stargazers_count":211,"watchers_count":211,"language":"Python","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":148,"mirror_url":null,"archived":false,"open_issues_count":32,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":148,"open_issues":32,"watchers":211,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/freeipa/freeipa/pulls/2241"},"html":{"href":"https://github.com/freeipa/freeipa/pull/2241"},"issue":{"href":"https://api.github.com/repos/freeipa/freeipa/issues/2241"},"comments":{"href":"https://api.github.com/repos/freeipa/freeipa/issues/2241/comments"},"review_comments":{"href":"https://api.github.com/repos/freeipa/freeipa/pulls/2241/comments"},"review_comment":{"href":"https://api.github.com/repos/freeipa/freeipa/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/freeipa/freeipa/pulls/2241/commits"},"statuses":{"href":"https://api.github.com/repos/freeipa/freeipa/statuses/b4a3a06f5ed481bf3f00146bd8a40f2a5b1ec747"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"tiran"}}