mizozobu 7 лет назад
Родитель
Сommit
c14f367262

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

@@ -309,6 +309,7 @@
     "recommended": "Recommended",
     "recommended": "Recommended",
     "username_email_password": "Username, Email and Password authentication",
     "username_email_password": "Username, Email and Password authentication",
     "ldap_auth": "LDAP authentication",
     "ldap_auth": "LDAP authentication",
+    "saml_auth": "SAML authentication",
     "google_auth2": "Google OAuth authentication",
     "google_auth2": "Google OAuth authentication",
     "google_auth2_by_crowi_desc": "However, this feature does not create new users, butit only makes it possible to login to the existing user who set up the association.",
     "google_auth2_by_crowi_desc": "However, this feature does not create new users, butit only makes it possible to login to the existing user who set up the association.",
     "facebook_auth2": "Facebook OAuth authentication",
     "facebook_auth2": "Facebook OAuth authentication",
@@ -374,6 +375,12 @@
       "group_search_user_DN_property_detail": "The property of user object to use in <code>&#123;&#123;dn&#125;&#125;</code> interpolation of <code>Group Search Filter</code>.",
       "group_search_user_DN_property_detail": "The property of user object to use in <code>&#123;&#123;dn&#125;&#125;</code> interpolation of <code>Group Search Filter</code>.",
       "test_config": "Test Saved Configuration"
       "test_config": "Test Saved Configuration"
     },
     },
+    "SAML": {
+      "name": "SAML",
+      "entry_point": "Entry Point",
+      "issuer": "Issuer",
+      "mapping_detail": "Specification of mappings for %s when creating new users"
+    },
     "OAuth": {
     "OAuth": {
       "register": "Register for %s",
       "register": "Register for %s",
       "change_redirect_url": "Enter <code>%s</code> <br>(where <code>%s</code> is your host name) for \"Authorized redirect URIs\".",
       "change_redirect_url": "Enter <code>%s</code> <br>(where <code>%s</code> is your host name) for \"Authorized redirect URIs\".",

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

@@ -328,6 +328,7 @@
     "recommended": "推奨",
     "recommended": "推奨",
     "username_email_password": "ユーザー名、Eメール、パスワードでの認証",
     "username_email_password": "ユーザー名、Eメール、パスワードでの認証",
     "ldap_auth": "LDAP 認証",
     "ldap_auth": "LDAP 認証",
+    "saml_auth": "SAML 認証",
     "google_auth2": "Google OAuth 認証",
     "google_auth2": "Google OAuth 認証",
     "google_auth2_by_crowi_desc": "ただし、この機能では新たなユーザーは作成されず、関連付け設定を行った既存ユーザーをログインできるようにするだけです。",
     "google_auth2_by_crowi_desc": "ただし、この機能では新たなユーザーは作成されず、関連付け設定を行った既存ユーザーをログインできるようにするだけです。",
     "facebook_auth2": "Facebook OAuth 認証",
     "facebook_auth2": "Facebook OAuth 認証",
@@ -392,6 +393,12 @@
       "group_search_user_DN_property_detail": "<code>グループ検索フィルター</code> 内の <code>&#123;&#123;dn&#125;&#125;</code> で置換される、ユーザーオブジェクトのプロパティー",
       "group_search_user_DN_property_detail": "<code>グループ検索フィルター</code> 内の <code>&#123;&#123;dn&#125;&#125;</code> で置換される、ユーザーオブジェクトのプロパティー",
       "test_config": "ログインテスト"
       "test_config": "ログインテスト"
     },
     },
+    "SAML": {
+      "name": "SAML",
+      "entry_point": "エントリーポイント",
+      "issuer": "発行者",
+      "mapping_detail": "新規ユーザーの%sに関連付ける属性"
+    },
     "OAuth": {
     "OAuth": {
       "register": "%sに登録",
       "register": "%sに登録",
       "change_redirect_url": "承認済みのリダイレクトURLに、 <code>%s</code> を入力<br>(<code>%s</code>は環境に合わせて変更してください)",
       "change_redirect_url": "承認済みのリダイレクトURLに、 <code>%s</code> を入力<br>(<code>%s</code>は環境に合わせて変更してください)",

+ 8 - 0
src/client/styles/scss/_login.scss

@@ -176,6 +176,14 @@
       background-color: #555;
       background-color: #555;
     }
     }
   }
   }
+  .btn-login-oauth.fcbtn#saml {
+    .btn-label {
+      background-color: rgba(#55a79a, 0.4);
+    }
+    &:after {
+      background-color: #555;
+    }
+  }
   .btn-register.fcbtn {
   .btn-register.fcbtn {
     .btn-label {
     .btn-label {
       background-color: rgba($brand-success, 0.4);
       background-color: rgba($brand-success, 0.4);

+ 9 - 1
src/server/views/admin/security.html

@@ -130,6 +130,7 @@
               <ul>
               <ul>
                 <li>{{ t("security_setting.username_email_password") }}</li>
                 <li>{{ t("security_setting.username_email_password") }}</li>
                 <li>{{ t("security_setting.ldap_auth") }}</li>
                 <li>{{ t("security_setting.ldap_auth") }}</li>
+                <li>{{ t("security_setting.saml_auth") }}</li>
                 <li>{{ t("security_setting.google_auth2") }}</li>
                 <li>{{ t("security_setting.google_auth2") }}</li>
                 <li>{{ t("security_setting.github_auth2") }}</li>
                 <li>{{ t("security_setting.github_auth2") }}</li>
                 <li>{{ t("security_setting.twitter_auth2") }}</li>
                 <li>{{ t("security_setting.twitter_auth2") }}</li>
@@ -245,6 +246,9 @@
             <li class="active">
             <li class="active">
               <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
               <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
             </li>
             </li>
+            <li>
+              <a href="#passport-saml" data-toggle="tab" role="tab"><i class="fa fa-key"></i> SAML</a>
+            </li>
             <li>
             <li>
               <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i class="fa fa-google"></i> Google</a>
               <a href="#passport-google-oauth" data-toggle="tab" role="tab"><i class="fa fa-google"></i> Google</a>
             </li>
             </li>
@@ -264,6 +268,10 @@
               {% include './widget/passport/ldap.html' with { settingForm: settingForm } %}
               {% include './widget/passport/ldap.html' with { settingForm: settingForm } %}
             </div>
             </div>
 
 
+            <div id="passport-saml" class="tab-pane" role="tabpanel" >
+              {% include './widget/passport/saml.html' %}
+            </div>
+
             <div id="passport-google-oauth" class="tab-pane" role="tabpanel">
             <div id="passport-google-oauth" class="tab-pane" role="tabpanel">
               {% include './widget/passport/google-oauth.html' %}
               {% include './widget/passport/google-oauth.html' %}
             </div>
             </div>
@@ -288,7 +296,7 @@
   </div>
   </div>
 
 
   <script>
   <script>
-    $('#generalSetting, #googleSetting, #mechanismSetting, #githubSetting, #twitterSetting').each(function() {
+    $('#generalSetting, #samlSetting, #googleSetting, #mechanismSetting, #githubSetting, #twitterSetting').each(function() {
       $(this).submit(function()
       $(this).submit(function()
       {
       {
         function showMessage(formId, msg, status) {
         function showMessage(formId, msg, status) {

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

@@ -0,0 +1,170 @@
+<form action="/_api/admin/security/passport-saml" method="post" class="form-horizontal passportStrategy" id="samlSetting" role="form"
+    {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+  <legend class="alert-anchor">{{ t("security_setting.SAML.name") }} {{ t("security_setting.configuration") }}</legend>
+
+  {% set nameForIsSamlEnabled = "settingForm[security:passport-saml:isEnabled]" %}
+  {% set isSamlEnabled = settingForm['security:passport-saml:isEnabled'] %}
+
+  <div class="form-group">
+    <label for="{{nameForIsSamlEnabled}}" class="col-xs-3 control-label">{{ t("security_setting.SAML.name") }}</label>
+    <div class="col-xs-6">
+      <div class="btn-group btn-toggle" data-toggle="buttons">
+        <label class="btn btn-default btn-rounded btn-outline {% if isSamlEnabled %}active{% endif %}" data-active-class="primary">
+          <input name="{{nameForIsSamlEnabled}}" value="true" type="radio"
+              {% if true === isSamlEnabled %}checked{% endif %}> ON
+        </label>
+        <label class="btn btn-default btn-rounded btn-outline {% if !isSamlEnabled %}active{% endif %}" data-active-class="default">
+          <input name="{{nameForIsSamlEnabled}}" value="false" type="radio"
+              {% if !isSamlEnabled %}checked{% endif %}> OFF
+        </label>
+      </div>
+    </div>
+  </div>
+  <fieldset id="passport-saml-hide-when-disabled" {%if !isSamlEnabled %}style="display: none;"{% endif %}>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:clientId]" class="col-xs-3 control-label">{{ t("security_setting.SAML.entry_point") }}</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" name="settingForm[security:passport-saml:clientId]" value="{{ settingForm['security:passport-saml:clientId'] || '' }}">
+        <p class="help-block">
+          <small>
+            {{ t("security_setting.Use env var if empty", "SAML_ENTRY_POINT") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:callbackUrl]" class="col-xs-3 control-label">{{ t("security_setting.callback_URL") }}</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" name="settingForm[security:passport-saml:callbackUrl]" value="{{ settingForm['security:passport-saml:callbackUrl'] || '' }}"
+            placeholder="http(s)://${growi.host}/passport/saml/callback">
+        <p class="help-block">
+          Input <code>http(s)://${growi.host}/passport/saml/callback</code><br>
+          <small>
+            {{ t("security_setting.Use env var if empty", "SAML_ISSUER") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:issuer]" class="col-xs-3 control-label">{{ t("security_setting.SAML.issuer") }}</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" name="settingForm[security:passport-saml:issuer]" value="{{ settingForm['security:passport-saml:issuer'] || '' }}">
+        <p class="help-block">
+          <small>
+            {{ t("security_setting.Use env var if empty", "SAML_ISSUER") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+    <h4>Attribute Mapping</h4>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:attrMapId]" class="col-xs-3 control-label">User ID</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" placeholder="Default: id"
+            name="settingForm[security:passport-saml:attrMapId]" value="{{ settingForm['security:passport-saml:attrMapId'] || '' }}">
+        <p class="help-block">
+          <small>
+            {{ t("security_setting.SAML.mapping_detail", "User ID") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:attrMapUsername]" class="col-xs-3 control-label">Username</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" placeholder="Default: username"
+            name="settingForm[security:passport-saml:attrMapUsername]" value="{{ settingForm['security:passport-saml:attrMapUsername'] || '' }}">
+        <p class="help-block">
+          <small>
+            {{ t("security_setting.SAML.mapping_detail", "Username") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <div class="col-xs-6 col-xs-offset-3">
+        <div class="checkbox checkbox-info">
+          <input type="checkbox" id="bindByUserName-SAML" name="settingForm[security:passport-saml:isSameUsernameTreatedAsIdenticalUser]" value="1"
+              {% if settingForm['security:passport-saml:isSameUsernameTreatedAsIdenticalUser'] %}checked{% endif %} />
+          <label for="bindByUserName-SAML">
+            {{ t("security_setting.Treat username matching as identical", "username") }}
+          </label>
+          <p class="help-block">
+            <small>
+              {{ t("security_setting.Treat username matching as identical_warn", "username") }}
+            </small>
+          </p>
+        </div>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:attrMapMail]" class="col-xs-3 control-label">Mail</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" placeholder="Default: mail"
+            name="settingForm[security:passport-saml:attrMapMail]" value="{{ settingForm['security:passport-saml:attrMapMail'] || '' }}">
+        <p class="help-block">
+          <small>
+            {{ t("security_setting.SAML.mapping_detail", "Email") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:attrMapFirstName]" class="col-xs-3 control-label">First Name</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" placeholder="Default: firstName"
+            name="settingForm[security:passport-saml:attrMapFirstName]" value="{{ settingForm['security:passport-saml:attrMapFirstName'] || '' }}">
+        <p class="help-block">
+          <small>
+            {{ t("security_setting.SAML.mapping_detail", "First Name") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+    <div class="form-group">
+      <label for="settingForm[security:passport-saml:attrMapLastName]" class="col-xs-3 control-label">Last Name</label>
+      <div class="col-xs-6">
+        <input class="form-control" type="text" placeholder="Default: lastName"
+            name="settingForm[security:passport-saml:attrMapLastName]" value="{{ settingForm['security:passport-saml:attrMapLastName'] || '' }}">
+        <p class="help-block">
+          <small>
+            {{ t("security_setting.SAML.mapping_detail", "Last Name") }}
+          </small>
+        </p>
+      </div>
+    </div>
+
+  </fieldset>
+
+  <div class="form-group" id="btn-update">
+    <div class="col-xs-offset-3 col-xs-6">
+      <input type="hidden" name="_csrf" value="{{ csrf() }}">
+      <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
+    </div>
+  </div>
+
+</form>
+
+<script>
+  $('input[name="settingForm[security:passport-saml:isEnabled]"]').change(function() {
+    const isEnabled = ($(this).val() === "true");
+
+    if (isEnabled) {
+      $('#passport-saml-hide-when-disabled').show(400);
+    }
+    else {
+      $('#passport-saml-hide-when-disabled').hide(400);
+    }
+  });
+</script>
+

+ 11 - 2
src/server/views/login.html

@@ -144,8 +144,7 @@
           </form>
           </form>
         </div>
         </div>
         {% endif %}
         {% endif %}
-
-        {% if passportGoogleLoginEnabled() || passportGitHubLoginEnabled() || passportFacebookLoginEnabled() || passportTwitterLoginEnabled() %}
+        {% if passportGoogleLoginEnabled() || passportGitHubLoginEnabled() || passportFacebookLoginEnabled() || passportTwitterLoginEnabled() || passportSamlLoginEnabled() %}
         <hr class="mb-1">
         <hr class="mb-1">
         <div class="collapse collapse-oauth collapse-anchor">
         <div class="collapse collapse-oauth collapse-anchor">
           <div class="spacer"></div>
           <div class="spacer"></div>
@@ -189,6 +188,16 @@
               <div class="small text-right">by Twitter Account</div>
               <div class="small text-right">by Twitter Account</div>
             </form>
             </form>
             {% endif %}
             {% endif %}
+            {% if passportSamlLoginEnabled() %}
+            <form role="form" action="/passport/saml" class="d-inline-flex flex-column">
+              <input type="hidden" name="_csrf" value="{{ csrf() }}">
+              <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-inline-flex" id="saml">
+                <span class="btn-label"><i class="fa fa-key"></i></span>
+                <span class="btn-label-text">{{ t('Sign in') }}</span>
+              </button>
+              <div class="small text-right">with SAML</div>
+            </form>
+            {% endif %}
           </div>{# ./d-flex flex-row flex-wrap #}
           </div>{# ./d-flex flex-row flex-wrap #}
           <div class="spacer"></div>
           <div class="spacer"></div>
         </div>
         </div>