{"action":"created","comment":{"url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments/28230324","id":28230324,"diff_hunk":"@@ -324,6 +335,242 @@ var updateAdminStatus = module.exports.updateAdminStatus = function(ctx, userId,\n     });\n };\n \n+/**\n+ * Import users using a CSV file.\n+ *\n+ * The CSV file should be formatted in the following way:\n+ *\n+ *  `displayName, email, shibbolethId`\n+ *\n+ * When importing a set of users using the local authentication strategy, the CSV format should be the following:\n+ *\n+ *  `displayName, email, password`\n+ *\n+ * @param  {Context}        ctx                             Standard context containing the current user and the current app\n+ * @param  {File}           file                            The CSV file to import\n+ * @param  {Number}         appId                           The id of the application into which the users should be imported\n+ * @param  {String}         authenticationStrategy          The authentication strategy for the user. One of `local` or `shibboleth`\n+ * @param  {Boolean}        [forceProfileUpdate]            Whether the user information should be updated, even when other user information is already present. Defaults to `false`\n+ * @param  {Function}       callback                        Standard callback function\n+ * @param  {Object}         callback.err                    An error that occurred, if any\n+ */\n+var importUsers = module.exports.importUsers = function(ctx, file, appId, authenticationStrategy, forceProfileUpdate, callback) {\n+    forceProfileUpdate = forceProfileUpdate || false;\n+    forceProfileUpdate = GrasshopperUtil.getBooleanParam(forceProfileUpdate);\n+    appId = GrasshopperUtil.getNumberParam(appId);\n+\n+    var validator = new Validator();\n+    validator.check(null, {'code': 401, 'msg': 'Only authenticated users can import users'}).isLoggedInUser(ctx);\n+    validator.check(authenticationStrategy, {'code': 400, 'msg': 'A valid authentication strategy must be provided'}).isIn(['local', 'shibboleth']);\n+    validator.check(null, {'code': 400, 'msg': 'A valid CSV file must be provided'}).isObject(file);\n+    if (file) {\n+        validator.check(file.file, {'code': 400, 'msg': 'Missing path to the CSV file'}).notEmpty();\n+    }\n+    validator.check(appId, {'code': 400, 'msg': 'A valid application id must be provided'}).isInt();\n+    validator.check(null, {'code': 400, 'msg': 'A valid profile update flag must be provided'}).isBoolean(forceProfileUpdate);\n+    if (validator.hasErrors()) {\n+        _cleanUpCSVFile(file, function() {\n+            return callback(validator.getFirstError());\n+        });\n+        return;\n+    }\n+\n+    // Ensure the application exists\n+    AppsAPI.getApp(ctx, appId, function(err, app) {\n+        if (err) {\n+            return callback(err);\n+        }\n+\n+        // Ensure the current user can import users\n+        UsersAuthz.canImportUsers(ctx, app, function(err, canImportUsers) {\n+            if (err) {\n+                return callback(err);\n+            } else if (!canImportUsers) {\n+                log().warn({'appId': appId, 'actor': ctx.user.id}, 'Unauthorized attempt at importing users');\n+                return callback({'code': 401, 'msg': 'You are not allowed to import users'});\n+            }\n+\n+            // Construct the CSV parser\n+            var parser = csv.parse({'trim': true}, function(err, userRows) {\n+                // Regardless of the error, get rid of the CSV file first\n+                _cleanUpCSVFile(file, function() {\n+                    if (err) {\n+                        log().error({'err': err, 'app': appId}, 'Failed to parse CSV file when importing users');","path":"node_modules/gh-users/lib/api.js","position":89,"original_position":89,"commit_id":"e40ede9bde94cb212da27108007b9f2cadc14612","original_commit_id":"e40ede9bde94cb212da27108007b9f2cadc14612","user":{"login":"mrvisser","id":102265,"avatar_url":"https://avatars.githubusercontent.com/u/102265?v=3","gravatar_id":"","url":"https://api.github.com/users/mrvisser","html_url":"https://github.com/mrvisser","followers_url":"https://api.github.com/users/mrvisser/followers","following_url":"https://api.github.com/users/mrvisser/following{/other_user}","gists_url":"https://api.github.com/users/mrvisser/gists{/gist_id}","starred_url":"https://api.github.com/users/mrvisser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mrvisser/subscriptions","organizations_url":"https://api.github.com/users/mrvisser/orgs","repos_url":"https://api.github.com/users/mrvisser/repos","events_url":"https://api.github.com/users/mrvisser/events{/privacy}","received_events_url":"https://api.github.com/users/mrvisser/received_events","type":"User","site_admin":false},"body":"`'appId': appId`","created_at":"2015-04-13T11:19:47Z","updated_at":"2015-04-13T11:19:47Z","html_url":"https://github.com/fronteerio/grasshopper/pull/86#discussion_r28230324","pull_request_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86","_links":{"self":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments/28230324"},"html":{"href":"https://github.com/fronteerio/grasshopper/pull/86#discussion_r28230324"},"pull_request":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86"}}},"pull_request":{"url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86","id":32610706,"html_url":"https://github.com/fronteerio/grasshopper/pull/86","diff_url":"https://github.com/fronteerio/grasshopper/pull/86.diff","patch_url":"https://github.com/fronteerio/grasshopper/pull/86.patch","issue_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/86","number":86,"state":"open","locked":false,"title":"Implemented the import users endpoint","user":{"login":"simong","id":92180,"avatar_url":"https://avatars.githubusercontent.com/u/92180?v=3","gravatar_id":"","url":"https://api.github.com/users/simong","html_url":"https://github.com/simong","followers_url":"https://api.github.com/users/simong/followers","following_url":"https://api.github.com/users/simong/following{/other_user}","gists_url":"https://api.github.com/users/simong/gists{/gist_id}","starred_url":"https://api.github.com/users/simong/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simong/subscriptions","organizations_url":"https://api.github.com/users/simong/orgs","repos_url":"https://api.github.com/users/simong/repos","events_url":"https://api.github.com/users/simong/events{/privacy}","received_events_url":"https://api.github.com/users/simong/received_events","type":"User","site_admin":false},"body":"This allows a global administrator to upload a CSV file and import a set of users. The request parameters AND csv file will be validated prior to actual\r\nuser creation/updating. The actual creation/updating happens async from the request.\r\n\r\n\r\nThis also brings in busboy as the new HTTP body parser","created_at":"2015-04-03T19:30:35Z","updated_at":"2015-04-13T11:19:47Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a67c1993531d6181e355eb823b0dc3cccd78608a","assignee":{"login":"mrvisser","id":102265,"avatar_url":"https://avatars.githubusercontent.com/u/102265?v=3","gravatar_id":"","url":"https://api.github.com/users/mrvisser","html_url":"https://github.com/mrvisser","followers_url":"https://api.github.com/users/mrvisser/followers","following_url":"https://api.github.com/users/mrvisser/following{/other_user}","gists_url":"https://api.github.com/users/mrvisser/gists{/gist_id}","starred_url":"https://api.github.com/users/mrvisser/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mrvisser/subscriptions","organizations_url":"https://api.github.com/users/mrvisser/orgs","repos_url":"https://api.github.com/users/mrvisser/repos","events_url":"https://api.github.com/users/mrvisser/events{/privacy}","received_events_url":"https://api.github.com/users/mrvisser/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/fronteerio/grasshopper/milestones/2","html_url":"https://github.com/fronteerio/grasshopper/milestones/Beta%20release","labels_url":"https://api.github.com/repos/fronteerio/grasshopper/milestones/2/labels","id":1021504,"number":2,"title":"Beta release","description":"","creator":{"login":"nicolaasmatthijs","id":109850,"avatar_url":"https://avatars.githubusercontent.com/u/109850?v=3","gravatar_id":"","url":"https://api.github.com/users/nicolaasmatthijs","html_url":"https://github.com/nicolaasmatthijs","followers_url":"https://api.github.com/users/nicolaasmatthijs/followers","following_url":"https://api.github.com/users/nicolaasmatthijs/following{/other_user}","gists_url":"https://api.github.com/users/nicolaasmatthijs/gists{/gist_id}","starred_url":"https://api.github.com/users/nicolaasmatthijs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nicolaasmatthijs/subscriptions","organizations_url":"https://api.github.com/users/nicolaasmatthijs/orgs","repos_url":"https://api.github.com/users/nicolaasmatthijs/repos","events_url":"https://api.github.com/users/nicolaasmatthijs/events{/privacy}","received_events_url":"https://api.github.com/users/nicolaasmatthijs/received_events","type":"User","site_admin":false},"open_issues":11,"closed_issues":3,"state":"open","created_at":"2015-03-14T14:57:03Z","updated_at":"2015-04-10T21:56:48Z","due_on":"2015-04-27T07:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86/commits","review_comments_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86/comments","review_comment_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments{/number}","comments_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/86/comments","statuses_url":"https://api.github.com/repos/fronteerio/grasshopper/statuses/e40ede9bde94cb212da27108007b9f2cadc14612","head":{"label":"fronteerio:users-import","ref":"users-import","sha":"e40ede9bde94cb212da27108007b9f2cadc14612","user":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"repo":{"id":24999124,"name":"grasshopper","full_name":"fronteerio/grasshopper","owner":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/fronteerio/grasshopper","description":"Grasshopper Event Engine","fork":false,"url":"https://api.github.com/repos/fronteerio/grasshopper","forks_url":"https://api.github.com/repos/fronteerio/grasshopper/forks","keys_url":"https://api.github.com/repos/fronteerio/grasshopper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fronteerio/grasshopper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fronteerio/grasshopper/teams","hooks_url":"https://api.github.com/repos/fronteerio/grasshopper/hooks","issue_events_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/events{/number}","events_url":"https://api.github.com/repos/fronteerio/grasshopper/events","assignees_url":"https://api.github.com/repos/fronteerio/grasshopper/assignees{/user}","branches_url":"https://api.github.com/repos/fronteerio/grasshopper/branches{/branch}","tags_url":"https://api.github.com/repos/fronteerio/grasshopper/tags","blobs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fronteerio/grasshopper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/refs{/sha}","trees_url":"https://api.github.com/repos/fronteerio/grasshopper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fronteerio/grasshopper/statuses/{sha}","languages_url":"https://api.github.com/repos/fronteerio/grasshopper/languages","stargazers_url":"https://api.github.com/repos/fronteerio/grasshopper/stargazers","contributors_url":"https://api.github.com/repos/fronteerio/grasshopper/contributors","subscribers_url":"https://api.github.com/repos/fronteerio/grasshopper/subscribers","subscription_url":"https://api.github.com/repos/fronteerio/grasshopper/subscription","commits_url":"https://api.github.com/repos/fronteerio/grasshopper/commits{/sha}","git_commits_url":"https://api.github.com/repos/fronteerio/grasshopper/git/commits{/sha}","comments_url":"https://api.github.com/repos/fronteerio/grasshopper/comments{/number}","issue_comment_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/comments{/number}","contents_url":"https://api.github.com/repos/fronteerio/grasshopper/contents/{+path}","compare_url":"https://api.github.com/repos/fronteerio/grasshopper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fronteerio/grasshopper/merges","archive_url":"https://api.github.com/repos/fronteerio/grasshopper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fronteerio/grasshopper/downloads","issues_url":"https://api.github.com/repos/fronteerio/grasshopper/issues{/number}","pulls_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls{/number}","milestones_url":"https://api.github.com/repos/fronteerio/grasshopper/milestones{/number}","notifications_url":"https://api.github.com/repos/fronteerio/grasshopper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fronteerio/grasshopper/labels{/name}","releases_url":"https://api.github.com/repos/fronteerio/grasshopper/releases{/id}","created_at":"2014-10-09T16:51:39Z","updated_at":"2015-04-12T16:49:13Z","pushed_at":"2015-04-12T23:05:56Z","git_url":"git://github.com/fronteerio/grasshopper.git","ssh_url":"git@github.com:fronteerio/grasshopper.git","clone_url":"https://github.com/fronteerio/grasshopper.git","svn_url":"https://github.com/fronteerio/grasshopper","homepage":null,"size":3998,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":21,"forks":4,"open_issues":21,"watchers":0,"default_branch":"master"}},"base":{"label":"fronteerio:master","ref":"master","sha":"eafeb1e7a444ab70feb1762309a601710b1c4f60","user":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"repo":{"id":24999124,"name":"grasshopper","full_name":"fronteerio/grasshopper","owner":{"login":"fronteerio","id":9116505,"avatar_url":"https://avatars.githubusercontent.com/u/9116505?v=3","gravatar_id":"","url":"https://api.github.com/users/fronteerio","html_url":"https://github.com/fronteerio","followers_url":"https://api.github.com/users/fronteerio/followers","following_url":"https://api.github.com/users/fronteerio/following{/other_user}","gists_url":"https://api.github.com/users/fronteerio/gists{/gist_id}","starred_url":"https://api.github.com/users/fronteerio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fronteerio/subscriptions","organizations_url":"https://api.github.com/users/fronteerio/orgs","repos_url":"https://api.github.com/users/fronteerio/repos","events_url":"https://api.github.com/users/fronteerio/events{/privacy}","received_events_url":"https://api.github.com/users/fronteerio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/fronteerio/grasshopper","description":"Grasshopper Event Engine","fork":false,"url":"https://api.github.com/repos/fronteerio/grasshopper","forks_url":"https://api.github.com/repos/fronteerio/grasshopper/forks","keys_url":"https://api.github.com/repos/fronteerio/grasshopper/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fronteerio/grasshopper/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fronteerio/grasshopper/teams","hooks_url":"https://api.github.com/repos/fronteerio/grasshopper/hooks","issue_events_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/events{/number}","events_url":"https://api.github.com/repos/fronteerio/grasshopper/events","assignees_url":"https://api.github.com/repos/fronteerio/grasshopper/assignees{/user}","branches_url":"https://api.github.com/repos/fronteerio/grasshopper/branches{/branch}","tags_url":"https://api.github.com/repos/fronteerio/grasshopper/tags","blobs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fronteerio/grasshopper/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fronteerio/grasshopper/git/refs{/sha}","trees_url":"https://api.github.com/repos/fronteerio/grasshopper/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fronteerio/grasshopper/statuses/{sha}","languages_url":"https://api.github.com/repos/fronteerio/grasshopper/languages","stargazers_url":"https://api.github.com/repos/fronteerio/grasshopper/stargazers","contributors_url":"https://api.github.com/repos/fronteerio/grasshopper/contributors","subscribers_url":"https://api.github.com/repos/fronteerio/grasshopper/subscribers","subscription_url":"https://api.github.com/repos/fronteerio/grasshopper/subscription","commits_url":"https://api.github.com/repos/fronteerio/grasshopper/commits{/sha}","git_commits_url":"https://api.github.com/repos/fronteerio/grasshopper/git/commits{/sha}","comments_url":"https://api.github.com/repos/fronteerio/grasshopper/comments{/number}","issue_comment_url":"https://api.github.com/repos/fronteerio/grasshopper/issues/comments{/number}","contents_url":"https://api.github.com/repos/fronteerio/grasshopper/contents/{+path}","compare_url":"https://api.github.com/repos/fronteerio/grasshopper/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fronteerio/grasshopper/merges","archive_url":"https://api.github.com/repos/fronteerio/grasshopper/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fronteerio/grasshopper/downloads","issues_url":"https://api.github.com/repos/fronteerio/grasshopper/issues{/number}","pulls_url":"https://api.github.com/repos/fronteerio/grasshopper/pulls{/number}","milestones_url":"https://api.github.com/repos/fronteerio/grasshopper/milestones{/number}","notifications_url":"https://api.github.com/repos/fronteerio/grasshopper/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fronteerio/grasshopper/labels{/name}","releases_url":"https://api.github.com/repos/fronteerio/grasshopper/releases{/id}","created_at":"2014-10-09T16:51:39Z","updated_at":"2015-04-12T16:49:13Z","pushed_at":"2015-04-12T23:05:56Z","git_url":"git://github.com/fronteerio/grasshopper.git","ssh_url":"git@github.com:fronteerio/grasshopper.git","clone_url":"https://github.com/fronteerio/grasshopper.git","svn_url":"https://github.com/fronteerio/grasshopper","homepage":null,"size":3998,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":21,"forks":4,"open_issues":21,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86"},"html":{"href":"https://github.com/fronteerio/grasshopper/pull/86"},"issue":{"href":"https://api.github.com/repos/fronteerio/grasshopper/issues/86"},"comments":{"href":"https://api.github.com/repos/fronteerio/grasshopper/issues/86/comments"},"review_comments":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86/comments"},"review_comment":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/fronteerio/grasshopper/pulls/86/commits"},"statuses":{"href":"https://api.github.com/repos/fronteerio/grasshopper/statuses/e40ede9bde94cb212da27108007b9f2cadc14612"}}}}