Răsfoiți Sursa

Merge pull request #693 from wadahiro/imprv/saml-account-link-by-email

Imprv/Add option to use email for account link when using SAML federation
Yuki Takei 7 ani în urmă
părinte
comite
5446a7afe6

+ 2 - 0
resource/locales/en-US/translation.json

@@ -352,6 +352,8 @@
     "optional": "Optional",
     "Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
     "Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
+    "Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>%s</code> match",
+    "Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>%s</code>.",
     "Use env var if empty": "Use env var <code>%s</code> if empty",
     "ldap": {
       "server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",

+ 2 - 0
resource/locales/ja/translation.json

@@ -368,6 +368,8 @@
     "optional": "オプション",
     "Treat username matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
     "Treat username matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
+    "Treat email matching as identical": "新規ログイン時、<code>%s</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
+    "Treat email matching as identical_warn": "警告: <code>%s</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
     "Use env var if empty": "空の場合、環境変数 <code>%s</code> を利用します",
     "ldap": {
       "server_url_detail": "LDAP URLを <code>ldap://host:port/DN</code> または <code>ldaps://host:port/DN</code> の形式で入力してください。",

+ 1 - 0
src/server/form/admin/securityPassportSaml.js

@@ -14,4 +14,5 @@ module.exports = form(
   field('settingForm[security:passport-saml:attrMapLastName]').trim(),
   field('settingForm[security:passport-saml:cert]').trim(),
   field('settingForm[security:passport-saml:isSameUsernameTreatedAsIdenticalUser]').trim().toBooleanStrict(),
+  field('settingForm[security:passport-saml:isSameEmailTreatedAsIdenticalUser]').trim().toBooleanStrict(),
 );

+ 6 - 0
src/server/models/config.js

@@ -75,6 +75,7 @@ module.exports = function(crowi) {
       'security:passport-ldap:groupDnProperty' : undefined,
       'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser': false,
       'security:passport-saml:isEnabled' : false,
+      'security:passport-saml:isSameEmailTreatedAsIdenticalUser': false,
       'security:passport-google:isEnabled' : false,
       'security:passport-github:isEnabled' : false,
       'security:passport-twitter:isEnabled' : false,
@@ -329,6 +330,11 @@ module.exports = function(crowi) {
     return getValueForCrowiNS(config, key);
   };
 
+  configSchema.statics.isSameEmailTreatedAsIdenticalUser = function(config, providerType) {
+    const key = `security:passport-${providerType}:isSameEmailTreatedAsIdenticalUser`;
+    return getValueForCrowiNS(config, key);
+  };
+
   configSchema.statics.isUploadable = function(config) {
     const method = process.env.FILE_UPLOAD || 'aws';
 

+ 16 - 2
src/server/models/external-account.js

@@ -67,10 +67,12 @@ class ExternalAccount {
    * @param {object} usernameToBeRegistered the username of User entity that will be created when accountId is not found
    * @param {object} nameToBeRegistered the name of User entity that will be created when accountId is not found
    * @param {object} mailToBeRegistered the mail of User entity that will be created when accountId is not found
+   * @param {boolean} isSameUsernameTreatedAsIdenticalUser
+   * @param {boolean} isSameEmailTreatedAsIdenticalUser
    * @returns {Promise<ExternalAccount>}
    * @memberof ExternalAccount
    */
-  static findOrRegister(providerType, accountId, usernameToBeRegistered, nameToBeRegistered, mailToBeRegistered) {
+  static findOrRegister(providerType, accountId, usernameToBeRegistered, nameToBeRegistered, mailToBeRegistered, isSameUsernameTreatedAsIdenticalUser, isSameEmailTreatedAsIdenticalUser) {
 
     return this.findOne({ providerType, accountId })
       .then(account => {
@@ -82,7 +84,19 @@ class ExternalAccount {
 
         const User = ExternalAccount.crowi.model('User');
 
-        return User.findOne({username: usernameToBeRegistered})
+        let promise = User.findOne({username: usernameToBeRegistered});
+        if (isSameUsernameTreatedAsIdenticalUser && isSameEmailTreatedAsIdenticalUser) {
+          promise = promise
+            .then(user => {
+              if (user == null) { return User.findOne({email: mailToBeRegistered}) }
+              return user;
+            });
+        }
+        else if (isSameEmailTreatedAsIdenticalUser) {
+          promise = User.findOne({email: mailToBeRegistered});
+        }
+
+        return promise
           .then(user => {
             // when the User that have the same `username` exists
             if (user != null) {

+ 6 - 3
src/server/routes/login-passport.js

@@ -427,6 +427,9 @@ module.exports = function(crowi, app) {
   };
 
   const getOrCreateUser = async(req, res, userInfo, providerId) => {
+    // get option
+    const isSameUsernameTreatedAsIdenticalUser = Config.isSameUsernameTreatedAsIdenticalUser(config, providerId);
+    const isSameEmailTreatedAsIdenticalUser = Config.isSameEmailTreatedAsIdenticalUser(config, providerId);
     try {
       // find or register(create) user
       const externalAccount = await ExternalAccount.findOrRegister(
@@ -435,14 +438,14 @@ module.exports = function(crowi, app) {
         userInfo.username,
         userInfo.name,
         userInfo.email,
+        isSameUsernameTreatedAsIdenticalUser,
+        isSameEmailTreatedAsIdenticalUser,
       );
       return externalAccount;
     }
     catch (err) {
       if (err.name === 'DuplicatedUsernameException') {
-        // get option
-        const isSameUsernameTreatedAsIdenticalUser = Config.isSameUsernameTreatedAsIdenticalUser(config, providerId);
-        if (isSameUsernameTreatedAsIdenticalUser) {
+        if (isSameEmailTreatedAsIdenticalUser || isSameUsernameTreatedAsIdenticalUser) {
           // associate to existing user
           debug(`ExternalAccount '${userInfo.username}' will be created and bound to the exisiting User account`);
           return ExternalAccount.associate(providerId, userInfo.id, err.user);

+ 17 - 0
src/server/views/admin/widget/passport/saml.html

@@ -117,6 +117,23 @@
       </div>
     </div>
 
+    <div class="form-group">
+      <div class="col-xs-6 col-xs-offset-3">
+        <div class="checkbox checkbox-info">
+          <input type="checkbox" id="bindByEmail-SAML" name="settingForm[security:passport-saml:isSameEmailTreatedAsIdenticalUser]" value="1"
+              {% if settingForm['security:passport-saml:isSameEmailTreatedAsIdenticalUser'] %}checked{% endif %} />
+          <label for="bindByEmail-SAML">
+            {{ t("security_setting.Treat email matching as identical", "email") }}
+          </label>
+          <p class="help-block">
+            <small>
+              {{ t("security_setting.Treat email matching as identical_warn", "email") }}
+            </small>
+          </p>
+        </div>
+      </div>
+    </div>
+
     <div class="form-group">
       <label for="settingForm[security:passport-saml:attrMapFirstName]" class="col-xs-3 control-label">{{ t("security_setting.SAML.First Name") }}</label>
       <div class="col-xs-6">