diff --git a/.meteor/packages b/.meteor/packages index cca6db9d0381..c4e0ccfffe57 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -78,3 +78,4 @@ raix:push@2.6.13-rc.1 jalik:ufs jalik:ufs-gridfs monbro:mongodb-mapreduce-aggregation +accounts-gitlab diff --git a/.meteor/versions b/.meteor/versions index 3e70bc7a002a..3ecea4402aa4 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,6 +1,7 @@ accounts-base@1.2.0 accounts-facebook@1.0.4 accounts-github@1.0.4 +accounts-gitlab@1.0.5-plugins.0 accounts-google@1.0.4 accounts-meteor-developer@1.0.4 accounts-oauth@1.1.5 @@ -31,6 +32,7 @@ fastclick@1.0.3 francocatena:status@1.3.0 geojson-utils@1.0.3 github@1.1.3 +gitlab@1.1.4-plugins.0 google@1.1.5 html-tools@1.0.4 htmljs@1.0.4 diff --git a/client/views/login/services.coffee b/client/views/login/services.coffee index d63b01e611b0..5f018df04853 100644 --- a/client/views/login/services.coffee +++ b/client/views/login/services.coffee @@ -5,7 +5,6 @@ Template.loginServices.helpers authServices = _.pluck ServiceConfiguration.configurations.find({}, { service: 1 }).fetch(), 'service' authServices.sort() - authServices.forEach (service) -> switch service when 'meteor-developer' @@ -14,6 +13,9 @@ Template.loginServices.helpers when 'github' serviceName = 'GitHub' icon = 'github-circled' + when 'gitlab' + serviceName = 'Gitlab' + icon = 'git-squared' # need to replace this with proper logo else serviceName = _.capitalize service icon = service @@ -49,13 +51,10 @@ Template.loginServices.events FlowRouter.go 'index' else loginWithService = "loginWith" + (if this.service is 'meteor-developer' then 'MeteorDeveloperAccount' else _.capitalize(this.service)) - serviceConfig = {} - Meteor[loginWithService] serviceConfig, (error) -> loadingIcon.addClass 'hidden' serviceIcon.removeClass 'hidden' - if error console.log JSON.stringify(error), error.message toastr.error error.message diff --git a/packages/accounts-gitlab/.gitignore b/packages/accounts-gitlab/.gitignore new file mode 100644 index 000000000000..677a6fc26373 --- /dev/null +++ b/packages/accounts-gitlab/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/accounts-gitlab/README.md b/packages/accounts-gitlab/README.md new file mode 100644 index 000000000000..8d902206a081 --- /dev/null +++ b/packages/accounts-gitlab/README.md @@ -0,0 +1,3 @@ +# accounts-gitlab + +A login service for Gitlab. Courtesy of the [Rocket.Chat](https://rocket.chat/) open source communications platform. See the [project page](https://www.meteor.com/accounts) on Meteor Accounts for more details. diff --git a/packages/accounts-gitlab/gitlab.js b/packages/accounts-gitlab/gitlab.js new file mode 100644 index 000000000000..6093b7b6105a --- /dev/null +++ b/packages/accounts-gitlab/gitlab.js @@ -0,0 +1,19 @@ +Accounts.oauth.registerService('gitlab'); + +if (Meteor.isClient) { + Meteor.loginWithGitlab = function(options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback); + Gitlab.requestCredential(options, credentialRequestCompleteCallback); + }; +} else { + Accounts.addAutopublishFields({ + forLoggedInUser: ['services.gitlab'], + forOtherUsers: ['services.gitlab.username'] + }); +} diff --git a/packages/accounts-gitlab/gitlab_login_button.css b/packages/accounts-gitlab/gitlab_login_button.css new file mode 100644 index 000000000000..d417a55741ad --- /dev/null +++ b/packages/accounts-gitlab/gitlab_login_button.css @@ -0,0 +1,3 @@ +#login-buttons-image-gitlab { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wJGBYxHYxl31wAAAHpSURBVDjLpZI/aFNRFMZ/973bJqGRPopV4qNq/+SpTYnWRhCKilShg9BGcHOM+GfQoZuLk4iLgw4qZNBaHLuIdBNHl7Ta1qdNFI3SihnaNG1MpH3vuiQYQnwZvHCG893zffc751z4z6PX5T5gA1DAKnAaOAQEgAfAVeCpl+CeCrlRuEC6maO4h0A1wl4tPAHMqNUthvrDdHYY7A3t4rDVjeO6rBU2FaABM1WCrBNoi48Mi+nH9yj+KtPibAKwJXfQ5vcRG7soUnYmWEuQgAEIYBv4cGpoILI0Z4tyYYPegS6UguyijZQ6J45GSNmZHzUcJYD2ii2Ajv7efZ8WZ6ZwXFj79hXpayW4O0SL1Nl/8jzZlZ9dQLFS70pgvZKIyGD0yvu5eRmMnrk1PjI81ir1qBACTdPevXj95mVuNX8XKDQc/+T334bZZ104cvzYw2s3J3qAL5WXSsDbf61NNMBu+wOBs+VSyQ84Nfhg028ZGx3/qyy0lC7lgi7lghBitoon03lvB8l0/k7Wnk+8mny0cyXzEcfZxgwfZPTyRMHsOzAFXE9YhtNQIJnOx4FpJXT1eSkn2g0frqMoFrfoCXcqlCOAGwnLuO/l4JymcWl5uRxzXUKghBAiZ5r+WaV4lrCM555zqO+x2d0ftGmpiA/0k70AAAAASUVORK5CYII=); +} diff --git a/packages/accounts-gitlab/package.js b/packages/accounts-gitlab/package.js new file mode 100644 index 000000000000..e5692d05fe40 --- /dev/null +++ b/packages/accounts-gitlab/package.js @@ -0,0 +1,16 @@ +Package.describe({ + summary: "Login service for Gitlab accounts", + version: "1.0.5-plugins.0" +}); + +Package.onUse(function(api) { + api.use('accounts-base', ['client', 'server']); + // Export Accounts (etc) to packages using this one. + api.imply('accounts-base', ['client', 'server']); + api.use('accounts-oauth', ['client', 'server']); + api.use('gitlab', ['client', 'server']); + + api.addFiles('gitlab_login_button.css', 'client'); + + api.addFiles("gitlab.js"); +}); diff --git a/packages/gitlab/.gitignore b/packages/gitlab/.gitignore new file mode 100644 index 000000000000..677a6fc26373 --- /dev/null +++ b/packages/gitlab/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/gitlab/README.md b/packages/gitlab/README.md new file mode 100644 index 000000000000..918f58dea0ed --- /dev/null +++ b/packages/gitlab/README.md @@ -0,0 +1,7 @@ +# gitlab + +An implementation of the GitLab OAuth2 flow. It works with your own private GitLab server instance. + +This software is supplied courtesy of the [Rocket.Chat](https://rocket.chat/) open source communications platform. + +See the [project page](https://www.meteor.com/accounts) on Meteor Accounts for more details. diff --git a/packages/gitlab/gitlab_client.js b/packages/gitlab/gitlab_client.js new file mode 100644 index 000000000000..ef5f04b997c0 --- /dev/null +++ b/packages/gitlab/gitlab_client.js @@ -0,0 +1,38 @@ +// Request Gitlab credentials for the user +// @param options {optional} +// @param credentialRequestCompleteCallback {Function} Callback function to call on +// completion. Takes one argument, credentialToken on success, or Error on +// error. +Gitlab.requestCredential = function (options, credentialRequestCompleteCallback) { + // support both (options, callback) and (callback). + if (!credentialRequestCompleteCallback && typeof options === 'function') { + credentialRequestCompleteCallback = options; + options = {}; + } + + var config = ServiceConfiguration.configurations.findOne({service: 'gitlab'}); + if (!config) { + credentialRequestCompleteCallback && credentialRequestCompleteCallback( + new ServiceConfiguration.ConfigError()); + return; + } + + var credentialToken = Random.secret(); + var loginStyle = OAuth._loginStyle('gitlab', config, options); + + var loginUrl = + Gitlab.ServerURL + '/oauth/authorize' + + '?client_id=' + config.clientId + + '&redirect_uri=' + OAuth._redirectUri('gitlab', config) + + '&response_type=code' + + '&state=' + OAuth._stateParam(loginStyle, credentialToken, options.redirectUrl); + + OAuth.launchLogin({ + loginService: "gitlab", + loginStyle: loginStyle, + loginUrl: loginUrl, + credentialRequestCompleteCallback: credentialRequestCompleteCallback, + credentialToken: credentialToken, + popupOptions: {width: 900, height: 450} + }); +}; diff --git a/packages/gitlab/gitlab_common.js b/packages/gitlab/gitlab_common.js new file mode 100644 index 000000000000..fbda5163a110 --- /dev/null +++ b/packages/gitlab/gitlab_common.js @@ -0,0 +1,3 @@ +Gitlab = {}; + +Gitlab.ServerURL = 'http://corei3:3000'; // this needs to be configured from Settings diff --git a/packages/gitlab/gitlab_configure.html b/packages/gitlab/gitlab_configure.html new file mode 100644 index 000000000000..8e893cc5ab98 --- /dev/null +++ b/packages/gitlab/gitlab_configure.html @@ -0,0 +1,14 @@ + diff --git a/packages/gitlab/gitlab_configure.js b/packages/gitlab/gitlab_configure.js new file mode 100644 index 000000000000..a7654009d387 --- /dev/null +++ b/packages/gitlab/gitlab_configure.js @@ -0,0 +1,12 @@ +Template.configureLoginServiceDialogForGitlab.helpers({ + siteUrl: function () { + return Meteor.absoluteUrl(); + } +}); + +Template.configureLoginServiceDialogForGitlab.fields = function () { + return [ + {property: 'clientId', label: 'Client ID'}, + {property: 'secret', label: 'Client Secret'} + ]; +}; diff --git a/packages/gitlab/gitlab_server.js b/packages/gitlab/gitlab_server.js new file mode 100644 index 000000000000..ab16b0fac2fc --- /dev/null +++ b/packages/gitlab/gitlab_server.js @@ -0,0 +1,77 @@ +OAuth.registerService('gitlab', 2, null, function(query) { + + var accessToken = getAccessToken(query); + console.log('at: ' + accessToken); + var identity = getIdentity(accessToken); + console.log('id: ' + JSON.stringify(identity)); + var primaryEmail = identity.email; + console.log('primay: ' + JSON.stringify(primaryEmail)); + + return { + serviceData: { + id: identity.id, + accessToken: OAuth.sealSecret(accessToken), + email: identity.email || '', + username: identity.username, + emails: [identity.email] + }, + options: {profile: {name: identity.username}} + }; +}); + + +var userAgent = "Meteor"; +if (Meteor.release) + userAgent += "/" + Meteor.release; + +var getAccessToken = function (query) { + var config = ServiceConfiguration.configurations.findOne({service: 'gitlab'}); + if (!config) + throw new ServiceConfiguration.ConfigError(); + + + var response; + try { + response = HTTP.post( + Gitlab.ServerURL + "/oauth/token", { + headers: { + Accept: 'application/json', + "User-Agent": userAgent + }, + params: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('gitlab', config), + grant_type: 'authorization_code', + state: query.state + } + }); + } catch (err) { + throw _.extend(new Error("Failed to complete OAuth handshake with Gitlab. " + err.message), + {response: err.response}); + } + if (response.data.error) { // if the http response was a json object with an error attribute + throw new Error("Failed to complete OAuth handshake with Gitlab. " + response.data.error); + } else { + return response.data.access_token; + } +}; + +var getIdentity = function (accessToken) { + try { + return HTTP.get( + Gitlab.ServerURL + "/api/v3/user", { + headers: {"User-Agent": userAgent}, // http://doc.gitlab.com/ce/api/users.html#Current-user + params: {access_token: accessToken} + }).data; + } catch (err) { + throw _.extend(new Error("Failed to fetch identity from Gitlab. " + err.message), + {response: err.response}); + } +}; + + +Gitlab.retrieveCredential = function(credentialToken, credentialSecret) { + return OAuth.retrieveCredential(credentialToken, credentialSecret); +}; diff --git a/packages/gitlab/package.js b/packages/gitlab/package.js new file mode 100644 index 000000000000..740f9259e5b2 --- /dev/null +++ b/packages/gitlab/package.js @@ -0,0 +1,23 @@ +Package.describe({ + summary: "Gitlab OAuth flow", + version: "1.1.4-plugins.0" +}); + +Package.onUse(function(api) { + api.use('oauth2', ['client', 'server']); + api.use('oauth', ['client', 'server']); + api.use('http', ['server']); + api.use('underscore', 'client'); + api.use('templating', 'client'); + api.use('random', 'client'); + api.use('service-configuration', ['client', 'server']); + + api.export('Gitlab'); + + api.addFiles( + ['gitlab_configure.html', 'gitlab_configure.js'], + 'client'); + + api.addFiles(['gitlab_common.js','gitlab_server.js'], 'server'); + api.addFiles(['gitlab_common.js','gitlab_client.js'], 'client'); +}); diff --git a/packages/rocketchat-lib/settings/server/startup.coffee b/packages/rocketchat-lib/settings/server/startup.coffee index 5fd5d4dc161f..cf64c9043cf5 100644 --- a/packages/rocketchat-lib/settings/server/startup.coffee +++ b/packages/rocketchat-lib/settings/server/startup.coffee @@ -12,6 +12,10 @@ Meteor.startup -> RocketChat.settings.add 'Accounts_Github', false, { type: 'boolean', group: 'Accounts' } RocketChat.settings.add 'Accounts_Github_id', '', { type: 'string', group: 'Accounts' } RocketChat.settings.add 'Accounts_Github_secret', '', { type: 'string', group: 'Accounts' } + RocketChat.settings.add 'Accounts_Gitlab', false, { type: 'boolean', group: 'Accounts' } + RocketChat.settings.add 'Accounts_Gitlab_id', '', { type: 'string', group: 'Accounts' } + RocketChat.settings.add 'Accounts_Gitlab_secret', '', { type: 'string', group: 'Accounts' } + RocketChat.settings.add 'Accounts_Linkedin', false, { type: 'boolean', group: 'Accounts' } RocketChat.settings.add 'Accounts_Linkedin_id', '', { type: 'string', group: 'Accounts' } RocketChat.settings.add 'Accounts_Linkedin_secret', '', { type: 'string', group: 'Accounts' } diff --git a/packages/rocketchat-lib/settings/server/updateServices.coffee b/packages/rocketchat-lib/settings/server/updateServices.coffee index af1dc359459f..1508deae1433 100644 --- a/packages/rocketchat-lib/settings/server/updateServices.coffee +++ b/packages/rocketchat-lib/settings/server/updateServices.coffee @@ -8,6 +8,7 @@ updateServices = -> 'facebook': 'Facebook' 'google': 'Google' 'github': 'Github' + 'gitlab': 'Gitlab' 'linkedin': 'Linkedin' 'meteor-developer': 'Meteor' 'twitter': 'Twitter' diff --git a/server/configuration/accounts_meld.coffee b/server/configuration/accounts_meld.coffee index 9e7ae88bd08d..d9c74045f809 100644 --- a/server/configuration/accounts_meld.coffee +++ b/server/configuration/accounts_meld.coffee @@ -1,6 +1,6 @@ orig_updateOrCreateUserFromExternalService = Accounts.updateOrCreateUserFromExternalService Accounts.updateOrCreateUserFromExternalService = (serviceName, serviceData, options) -> - if serviceName not in ['facebook', 'github', 'google', 'meteor-developer', 'linkedin', 'twitter'] + if serviceName not in ['facebook', 'github', 'gitlab', 'google', 'meteor-developer', 'linkedin', 'twitter'] return if serviceName is 'meteor-developer' @@ -17,7 +17,7 @@ Accounts.updateOrCreateUserFromExternalService = (serviceName, serviceData, opti serviceData.email = serviceData.emailAddress if serviceData.email - + # Remove not verified users that have same email notVerifiedUser = Meteor.users.remove({emails: {$elemMatch: {address: serviceData.email, verified: false}}}) diff --git a/server/lib/accounts.coffee b/server/lib/accounts.coffee index 6667d7bb13f0..5c6779ad995b 100644 --- a/server/lib/accounts.coffee +++ b/server/lib/accounts.coffee @@ -30,12 +30,14 @@ Accounts.onCreateUser (options, user) -> serviceName = 'google' else if user.services?.github? serviceName = 'github' + else if user.services?.gitlab? + serviceName = 'gitlab' else if user.services?['meteor-developer']? serviceName = 'meteor-developer' else if user.services?.twitter? serviceName = 'twitter' - if serviceName in ['facebook', 'google', 'meteor-developer', 'github', 'twitter'] + if serviceName in ['facebook', 'google', 'meteor-developer', 'github', 'gitlab', 'twitter'] if not user?.name? or user.name is '' if options.profile?.name? user.name = options.profile?.name