Browse Source

#1633 azure ad support

Shinsuke Sugaya 6 năm trước cách đây
mục cha
commit
947786fcb8

+ 15 - 0
pom.xml

@@ -1332,6 +1332,21 @@
 				</exclusion>
 				</exclusion>
 			</exclusions>
 			</exclusions>
 		</dependency>
 		</dependency>
+		<dependency>
+			<groupId>com.microsoft.azure</groupId>
+			<artifactId>adal4j</artifactId>
+			<version>1.6.3</version>
+			<exclusions>
+				<exclusion>
+					<groupId>com.github.stephenc.jcip</groupId>
+					<artifactId>jcip-annotations</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>com.sun.mail</groupId>
+					<artifactId>javax.mail</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
 
 
 		<!-- suggest library -->
 		<!-- suggest library -->
 		<dependency>
 		<dependency>

+ 103 - 0
src/main/java/org/codelibs/fess/app/web/base/login/AzureAdCredential.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012-2019 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.base.login;
+
+import static org.codelibs.core.stream.StreamUtil.stream;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.codelibs.fess.entity.FessUser;
+import org.codelibs.fess.helper.SystemHelper;
+import org.codelibs.fess.util.ComponentUtil;
+import org.lastaflute.web.login.credential.LoginCredential;
+
+import com.microsoft.aad.adal4j.AuthenticationResult;
+
+public class AzureAdCredential implements LoginCredential, FessCredential {
+
+    private final AuthenticationResult authResult;
+
+    public AzureAdCredential(final AuthenticationResult authResult) {
+        this.authResult = authResult;
+    }
+
+    @Override
+    public String getUserId() {
+        return authResult.getUserInfo().getDisplayableId();
+    }
+
+    @Override
+    public String toString() {
+        return "{" + authResult.getUserInfo().getDisplayableId() + "}";
+    }
+
+    public User getUser() {
+        return new User(authResult.getUserInfo().getDisplayableId(), new String[0], new String[0]);
+    }
+
+    public static class User implements FessUser {
+        private static final long serialVersionUID = 1L;
+
+        protected final String name;
+
+        protected String[] groups;
+
+        protected String[] roles;
+
+        protected String[] permissions;
+
+        protected User(final String name, final String[] groups, final String[] roles) {
+            this.name = name;
+            this.groups = groups;
+            this.roles = roles;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String[] getRoleNames() {
+            return roles;
+        }
+
+        @Override
+        public String[] getGroupNames() {
+            return groups;
+        }
+
+        @Override
+        public String[] getPermissions() {
+            if (permissions == null) {
+                final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
+                final Set<String> permissionSet = new HashSet<>();
+                permissionSet.add(systemHelper.getSearchRoleByUser(name));
+                stream(groups).of(stream -> stream.forEach(s -> permissionSet.add(systemHelper.getSearchRoleByGroup(s))));
+                stream(roles).of(stream -> stream.forEach(s -> permissionSet.add(systemHelper.getSearchRoleByRole(s))));
+                permissions = permissionSet.toArray(new String[permissionSet.size()]);
+            }
+            return permissions;
+        }
+
+        @Override
+        public boolean isEditable() {
+            return false;
+        }
+
+    }
+}

+ 22 - 0
src/main/java/org/codelibs/fess/app/web/base/login/FessCredential.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright 2012-2019 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.base.login;
+
+public interface FessCredential {
+
+    String getUserId();
+
+}

+ 22 - 13
src/main/java/org/codelibs/fess/app/web/base/login/FessLoginAssist.java

@@ -15,6 +15,8 @@
  */
  */
 package org.codelibs.fess.app.web.base.login;
 package org.codelibs.fess.app.web.base.login;
 
 
+import java.util.function.Function;
+
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 
 
 import org.codelibs.fess.Constants;
 import org.codelibs.fess.Constants;
@@ -25,6 +27,7 @@ import org.codelibs.fess.es.user.exbhv.UserBhv;
 import org.codelibs.fess.exception.UserRoleLoginException;
 import org.codelibs.fess.exception.UserRoleLoginException;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.sso.SsoAuthenticator;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalEntity;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
@@ -34,7 +37,6 @@ import org.lastaflute.web.login.LoginHandlingResource;
 import org.lastaflute.web.login.PrimaryLoginManager;
 import org.lastaflute.web.login.PrimaryLoginManager;
 import org.lastaflute.web.login.TypicalLoginAssist;
 import org.lastaflute.web.login.TypicalLoginAssist;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
-import org.lastaflute.web.login.credential.UserPasswordCredential;
 import org.lastaflute.web.login.exception.LoginRequiredException;
 import org.lastaflute.web.login.exception.LoginRequiredException;
 import org.lastaflute.web.login.option.LoginSpecifiedOption;
 import org.lastaflute.web.login.option.LoginSpecifiedOption;
 import org.lastaflute.web.servlet.session.SessionManager;
 import org.lastaflute.web.servlet.session.SessionManager;
@@ -140,8 +142,8 @@ public class FessLoginAssist extends TypicalLoginAssist<String, FessUserBean, Fe
 
 
     @Override
     @Override
     protected void resolveCredential(final CredentialResolver resolver) {
     protected void resolveCredential(final CredentialResolver resolver) {
-        resolver.resolve(UserPasswordCredential.class, credential -> {
-            final UserPasswordCredential userCredential = credential;
+        resolver.resolve(LocalUserCredential.class, credential -> {
+            final LocalUserCredential userCredential = credential;
             final String username = userCredential.getUser();
             final String username = userCredential.getUser();
             final String password = userCredential.getPassword();
             final String password = userCredential.getPassword();
             if (!fessConfig.isAdminUser(username)) {
             if (!fessConfig.isAdminUser(username)) {
@@ -152,16 +154,23 @@ public class FessLoginAssist extends TypicalLoginAssist<String, FessUserBean, Fe
             }
             }
             return doFindLoginUser(username, encryptPassword(password));
             return doFindLoginUser(username, encryptPassword(password));
         });
         });
-        resolver.resolve(SpnegoCredential.class, credential -> {
-            final String username = credential.getUsername();
-            if (!fessConfig.isAdminUser(username)) {
-                return ComponentUtil.getLdapManager().login(username);
-            }
-            return OptionalEntity.empty();
-        });
-        resolver.resolve(OpenIdConnectCredential.class, credential -> {
-            return OptionalEntity.of(credential.getUser());
-        });
+        final LoginCredentialResolver loginResolver = new LoginCredentialResolver(resolver);
+        for (final SsoAuthenticator auth : ComponentUtil.getSsoManager().getAuthenticators()) {
+            auth.resolveCredential(loginResolver);
+        }
+    }
+
+    public static class LoginCredentialResolver {
+        private final TypicalLoginAssist<String, FessUserBean, FessUser>.CredentialResolver resolver;
+
+        public LoginCredentialResolver(final CredentialResolver resolver) {
+            this.resolver = resolver;
+        }
+
+        public <CREDENTIAL extends LoginCredential> void resolve(final Class<CREDENTIAL> credentialType,
+                final Function<CREDENTIAL, OptionalEntity<FessUser>> oneArgLambda) {
+            resolver.resolve(credentialType, credential -> oneArgLambda.apply(credential));
+        }
     }
     }
 
 
     protected OptionalEntity<FessUser> doFindLoginUser(final String username, final String cipheredPassword) {
     protected OptionalEntity<FessUser> doFindLoginUser(final String username, final String cipheredPassword) {

+ 29 - 0
src/main/java/org/codelibs/fess/app/web/base/login/LocalUserCredential.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012-2019 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.app.web.base.login;
+
+import org.lastaflute.web.login.credential.UserPasswordCredential;
+
+public class LocalUserCredential extends UserPasswordCredential implements FessCredential {
+    public LocalUserCredential(String user, String password) {
+        super(user, password);
+    }
+
+    @Override
+    public String getUserId() {
+        return getUser();
+    }
+}

+ 5 - 4
src/main/java/org/codelibs/fess/app/web/base/login/OpenIdConnectCredential.java

@@ -27,7 +27,7 @@ import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ComponentUtil;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
 
 
-public class OpenIdConnectCredential implements LoginCredential {
+public class OpenIdConnectCredential implements LoginCredential, FessCredential {
 
 
     private final Map<String, Object> attributes;
     private final Map<String, Object> attributes;
 
 
@@ -37,16 +37,17 @@ public class OpenIdConnectCredential implements LoginCredential {
 
 
     @Override
     @Override
     public String toString() {
     public String toString() {
-        return "{" + getEmail() + "}";
+        return "{" + getUserId() + "}";
     }
     }
 
 
-    public String getEmail() {
+    @Override
+    public String getUserId() {
         return (String) attributes.get("email");
         return (String) attributes.get("email");
     }
     }
 
 
     public User getUser() {
     public User getUser() {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        return new User(getEmail(), fessConfig.getOicDefaultGroupsAsArray(), fessConfig.getOicDefaultRolesAsArray());
+        return new User(getUserId(), fessConfig.getOicDefaultGroupsAsArray(), fessConfig.getOicDefaultRolesAsArray());
     }
     }
 
 
     public static class User implements FessUser {
     public static class User implements FessUser {

+ 6 - 6
src/main/java/org/codelibs/fess/app/web/base/login/SpnegoCredential.java

@@ -17,21 +17,21 @@ package org.codelibs.fess.app.web.base.login;
 
 
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
 
 
-public class SpnegoCredential implements LoginCredential {
+public class SpnegoCredential implements LoginCredential, FessCredential {
     private final String username;
     private final String username;
 
 
-    // private Principal principal;
-
     public SpnegoCredential(final String username) {
     public SpnegoCredential(final String username) {
         this.username = username;
         this.username = username;
     }
     }
 
 
+    @Override
+    public String getUserId() {
+        return username;
+    }
+
     @Override
     @Override
     public String toString() {
     public String toString() {
         return "{" + username + "}";
         return "{" + username + "}";
     }
     }
 
 
-    public String getUsername() {
-        return username;
-    }
 }
 }

+ 3 - 3
src/main/java/org/codelibs/fess/app/web/login/LoginAction.java

@@ -16,10 +16,10 @@
 package org.codelibs.fess.app.web.login;
 package org.codelibs.fess.app.web.login;
 
 
 import org.codelibs.fess.app.web.base.FessLoginAction;
 import org.codelibs.fess.app.web.base.FessLoginAction;
+import org.codelibs.fess.app.web.base.login.LocalUserCredential;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.codelibs.fess.util.RenderDataUtil;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
 import org.lastaflute.web.Execute;
 import org.lastaflute.web.Execute;
-import org.lastaflute.web.login.credential.UserPasswordCredential;
 import org.lastaflute.web.login.exception.LoginFailureException;
 import org.lastaflute.web.login.exception.LoginFailureException;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.response.HtmlResponse;
 
 
@@ -52,13 +52,13 @@ public class LoginAction extends FessLoginAction {
         final String password = form.password;
         final String password = form.password;
         form.clearSecurityInfo();
         form.clearSecurityInfo();
         try {
         try {
-            return fessLoginAssist.loginRedirect(new UserPasswordCredential(username, password), op -> {}, () -> {
+            return fessLoginAssist.loginRedirect(new LocalUserCredential(username, password), op -> {}, () -> {
                 activityHelper.login(getUserBean());
                 activityHelper.login(getUserBean());
                 userInfoHelper.deleteUserCodeFromCookie(request);
                 userInfoHelper.deleteUserCodeFromCookie(request);
                 return getHtmlResponse();
                 return getHtmlResponse();
             });
             });
         } catch (final LoginFailureException lfe) {
         } catch (final LoginFailureException lfe) {
-            activityHelper.loginFailure(OptionalThing.of(new UserPasswordCredential(username, password)));
+            activityHelper.loginFailure(OptionalThing.of(new LocalUserCredential(username, password)));
             throwValidationError(messages -> messages.addErrorsLoginError(GLOBAL), () -> asIndexPage(form));
             throwValidationError(messages -> messages.addErrorsLoginError(GLOBAL), () -> asIndexPage(form));
         }
         }
         return redirect(getClass());
         return redirect(getClass());

+ 2 - 2
src/main/java/org/codelibs/fess/app/web/profile/ProfileAction.java

@@ -23,9 +23,9 @@ import javax.annotation.Resource;
 
 
 import org.codelibs.fess.app.service.UserService;
 import org.codelibs.fess.app.service.UserService;
 import org.codelibs.fess.app.web.base.FessSearchAction;
 import org.codelibs.fess.app.web.base.FessSearchAction;
+import org.codelibs.fess.app.web.base.login.LocalUserCredential;
 import org.codelibs.fess.app.web.login.LoginAction;
 import org.codelibs.fess.app.web.login.LoginAction;
 import org.lastaflute.web.Execute;
 import org.lastaflute.web.Execute;
-import org.lastaflute.web.login.credential.UserPasswordCredential;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.validation.VaErrorHook;
 import org.lastaflute.web.validation.VaErrorHook;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
@@ -87,7 +87,7 @@ public class ProfileAction extends FessSearchAction {
             }, validationErrorLambda);
             }, validationErrorLambda);
         }
         }
 
 
-        fessLoginAssist.findLoginUser(new UserPasswordCredential(getUserBean().get().getUserId(), form.oldPassword)).orElseGet(() -> {
+        fessLoginAssist.findLoginUser(new LocalUserCredential(getUserBean().get().getUserId(), form.oldPassword)).orElseGet(() -> {
             throwValidationError(messages -> {
             throwValidationError(messages -> {
                 messages.addErrorsNoUserForChangingPassword(GLOBAL);
                 messages.addErrorsNoUserForChangingPassword(GLOBAL);
             }, validationErrorLambda);
             }, validationErrorLambda);

+ 3 - 13
src/main/java/org/codelibs/fess/helper/ActivityHelper.java

@@ -24,13 +24,11 @@ import java.util.stream.Collectors;
 import javax.annotation.PostConstruct;
 import javax.annotation.PostConstruct;
 
 
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.lang.StringUtil;
-import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
-import org.codelibs.fess.app.web.base.login.SpnegoCredential;
+import org.codelibs.fess.app.web.base.login.FessCredential;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.optional.OptionalThing;
 import org.dbflute.optional.OptionalThing;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
-import org.lastaflute.web.login.credential.UserPasswordCredential;
 import org.lastaflute.web.util.LaRequestUtil;
 import org.lastaflute.web.util.LaRequestUtil;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
@@ -74,18 +72,10 @@ public class ActivityHelper {
             buffer.append('\t');
             buffer.append('\t');
             buffer.append("class:");
             buffer.append("class:");
             buffer.append(c.getClass().getSimpleName());
             buffer.append(c.getClass().getSimpleName());
-            if (c instanceof OpenIdConnectCredential) {
+            if (c instanceof FessCredential) {
                 buffer.append('\t');
                 buffer.append('\t');
                 buffer.append("user:");
                 buffer.append("user:");
-                buffer.append(((OpenIdConnectCredential) c).getEmail());
-            } else if (c instanceof SpnegoCredential) {
-                buffer.append('\t');
-                buffer.append("user:");
-                buffer.append(((SpnegoCredential) c).getUsername());
-            } else if (c instanceof UserPasswordCredential) {
-                buffer.append('\t');
-                buffer.append("user:");
-                buffer.append(((UserPasswordCredential) c).getUser());
+                buffer.append(((FessCredential) c).getUserId());
             }
             }
             return buffer.toString();
             return buffer.toString();
         }).ifPresent(buf::append);
         }).ifPresent(buf::append);

+ 0 - 422
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -1397,72 +1397,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. homeDirectory */
     /** The key of the configuration. e.g. homeDirectory */
     String LDAP_ATTR_HOME_DIRECTORY = "ldap.attr.homeDirectory";
     String LDAP_ATTR_HOME_DIRECTORY = "ldap.attr.homeDirectory";
 
 
-    /** The key of the configuration. e.g. none */
-    String SSO_TYPE = "sso.type";
-
-    /** The key of the configuration. e.g.  */
-    String SPNEGO_LOGGER_LEVEL = "spnego.logger.level";
-
-    /** The key of the configuration. e.g. krb5.conf */
-    String SPNEGO_KRB5_CONF = "spnego.krb5.conf";
-
-    /** The key of the configuration. e.g. auth_login.conf */
-    String SPNEGO_LOGIN_CONF = "spnego.login.conf";
-
-    /** The key of the configuration. e.g. username */
-    String SPNEGO_PREAUTH_USERNAME = "spnego.preauth.username";
-
-    /** The key of the configuration. e.g. password */
-    String SPNEGO_PREAUTH_PASSWORD = "spnego.preauth.password";
-
-    /** The key of the configuration. e.g. spnego-client */
-    String SPNEGO_LOGIN_CLIENT_MODULE = "spnego.login.client.module";
-
-    /** The key of the configuration. e.g. spnego-server */
-    String SPNEGO_LOGIN_SERVER_MODULE = "spnego.login.server.module";
-
-    /** The key of the configuration. e.g. true */
-    String SPNEGO_ALLOW_BASIC = "spnego.allow.basic";
-
-    /** The key of the configuration. e.g. true */
-    String SPNEGO_ALLOW_UNSECURE_BASIC = "spnego.allow.unsecure.basic";
-
-    /** The key of the configuration. e.g. true */
-    String SPNEGO_PROMPT_NTLM = "spnego.prompt.ntlm";
-
-    /** The key of the configuration. e.g. true */
-    String SPNEGO_ALLOW_LOCALHOST = "spnego.allow.localhost";
-
-    /** The key of the configuration. e.g. false */
-    String SPNEGO_ALLOW_DELEGATION = "spnego.allow.delegation";
-
-    /** The key of the configuration. e.g.  */
-    String SPNEGO_EXCLUDE_DIRS = "spnego.exclude.dirs";
-
-    /** The key of the configuration. e.g. __CLIENT_ID__ */
-    String OIC_CLIENT_ID = "oic.client.id";
-
-    /** The key of the configuration. e.g. __CLIENT_SECRET__ */
-    String OIC_CLIENT_SECRET = "oic.client.secret";
-
-    /** The key of the configuration. e.g. https://accounts.google.com/o/oauth2/auth */
-    String OIC_AUTH_SERVER_URL = "oic.auth.server.url";
-
-    /** The key of the configuration. e.g. http://localhost:8080/sso/ */
-    String OIC_REDIRECT_URL = "oic.redirect.url";
-
-    /** The key of the configuration. e.g. openid email */
-    String OIC_SCOPE = "oic.scope";
-
-    /** The key of the configuration. e.g. https://accounts.google.com/o/oauth2/token */
-    String OIC_TOKEN_SERVER_URL = "oic.token.server.url";
-
-    /** The key of the configuration. e.g. guest */
-    String OIC_DEFAULT_ROLES = "oic.default.roles";
-
-    /** The key of the configuration. e.g.  */
-    String OIC_DEFAULT_GROUPS = "oic.default.groups";
-
     /**
     /**
      * Get the value of property as {@link String}.
      * Get the value of property as {@link String}.
      * @param propertyKey The key of the property. (NotNull)
      * @param propertyKey The key of the property. (NotNull)
@@ -5908,220 +5842,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
      */
     String getLdapAttrHomeDirectory();
     String getLdapAttrHomeDirectory();
 
 
-    /**
-     * Get the value for the key 'sso.type'. <br>
-     * The value is, e.g. none <br>
-     * comment: ------
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSsoType();
-
-    /**
-     * Get the value for the key 'spnego.logger.level'. <br>
-     * The value is, e.g.  <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoLoggerLevel();
-
-    /**
-     * Get the value for the key 'spnego.logger.level' as {@link Integer}. <br>
-     * The value is, e.g.  <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     * @throws NumberFormatException When the property is not integer.
-     */
-    Integer getSpnegoLoggerLevelAsInteger();
-
-    /**
-     * Get the value for the key 'spnego.krb5.conf'. <br>
-     * The value is, e.g. krb5.conf <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoKrb5Conf();
-
-    /**
-     * Get the value for the key 'spnego.login.conf'. <br>
-     * The value is, e.g. auth_login.conf <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoLoginConf();
-
-    /**
-     * Get the value for the key 'spnego.preauth.username'. <br>
-     * The value is, e.g. username <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoPreauthUsername();
-
-    /**
-     * Get the value for the key 'spnego.preauth.password'. <br>
-     * The value is, e.g. password <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoPreauthPassword();
-
-    /**
-     * Get the value for the key 'spnego.login.client.module'. <br>
-     * The value is, e.g. spnego-client <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoLoginClientModule();
-
-    /**
-     * Get the value for the key 'spnego.login.server.module'. <br>
-     * The value is, e.g. spnego-server <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoLoginServerModule();
-
-    /**
-     * Get the value for the key 'spnego.allow.basic'. <br>
-     * The value is, e.g. true <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoAllowBasic();
-
-    /**
-     * Is the property for the key 'spnego.allow.basic' true? <br>
-     * The value is, e.g. true <br>
-     * @return The determination, true or false. (if not found, exception but basically no way)
-     */
-    boolean isSpnegoAllowBasic();
-
-    /**
-     * Get the value for the key 'spnego.allow.unsecure.basic'. <br>
-     * The value is, e.g. true <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoAllowUnsecureBasic();
-
-    /**
-     * Is the property for the key 'spnego.allow.unsecure.basic' true? <br>
-     * The value is, e.g. true <br>
-     * @return The determination, true or false. (if not found, exception but basically no way)
-     */
-    boolean isSpnegoAllowUnsecureBasic();
-
-    /**
-     * Get the value for the key 'spnego.prompt.ntlm'. <br>
-     * The value is, e.g. true <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoPromptNtlm();
-
-    /**
-     * Is the property for the key 'spnego.prompt.ntlm' true? <br>
-     * The value is, e.g. true <br>
-     * @return The determination, true or false. (if not found, exception but basically no way)
-     */
-    boolean isSpnegoPromptNtlm();
-
-    /**
-     * Get the value for the key 'spnego.allow.localhost'. <br>
-     * The value is, e.g. true <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoAllowLocalhost();
-
-    /**
-     * Is the property for the key 'spnego.allow.localhost' true? <br>
-     * The value is, e.g. true <br>
-     * @return The determination, true or false. (if not found, exception but basically no way)
-     */
-    boolean isSpnegoAllowLocalhost();
-
-    /**
-     * Get the value for the key 'spnego.allow.delegation'. <br>
-     * The value is, e.g. false <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoAllowDelegation();
-
-    /**
-     * Is the property for the key 'spnego.allow.delegation' true? <br>
-     * The value is, e.g. false <br>
-     * @return The determination, true or false. (if not found, exception but basically no way)
-     */
-    boolean isSpnegoAllowDelegation();
-
-    /**
-     * Get the value for the key 'spnego.exclude.dirs'. <br>
-     * The value is, e.g.  <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSpnegoExcludeDirs();
-
-    /**
-     * Get the value for the key 'spnego.exclude.dirs' as {@link Integer}. <br>
-     * The value is, e.g.  <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     * @throws NumberFormatException When the property is not integer.
-     */
-    Integer getSpnegoExcludeDirsAsInteger();
-
-    /**
-     * Get the value for the key 'oic.client.id'. <br>
-     * The value is, e.g. __CLIENT_ID__ <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicClientId();
-
-    /**
-     * Get the value for the key 'oic.client.secret'. <br>
-     * The value is, e.g. __CLIENT_SECRET__ <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicClientSecret();
-
-    /**
-     * Get the value for the key 'oic.auth.server.url'. <br>
-     * The value is, e.g. https://accounts.google.com/o/oauth2/auth <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicAuthServerUrl();
-
-    /**
-     * Get the value for the key 'oic.redirect.url'. <br>
-     * The value is, e.g. http://localhost:8080/sso/ <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicRedirectUrl();
-
-    /**
-     * Get the value for the key 'oic.scope'. <br>
-     * The value is, e.g. openid email <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicScope();
-
-    /**
-     * Get the value for the key 'oic.token.server.url'. <br>
-     * The value is, e.g. https://accounts.google.com/o/oauth2/token <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicTokenServerUrl();
-
-    /**
-     * Get the value for the key 'oic.default.roles'. <br>
-     * The value is, e.g. guest <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicDefaultRoles();
-
-    /**
-     * Get the value for the key 'oic.default.groups'. <br>
-     * The value is, e.g.  <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getOicDefaultGroups();
-
-    /**
-     * Get the value for the key 'oic.default.groups' as {@link Integer}. <br>
-     * The value is, e.g.  <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     * @throws NumberFormatException When the property is not integer.
-     */
-    Integer getOicDefaultGroupsAsInteger();
-
     /**
     /**
      * The simple implementation for configuration.
      * The simple implementation for configuration.
      * @author FreeGen
      * @author FreeGen
@@ -8467,126 +8187,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return get(FessConfig.LDAP_ATTR_HOME_DIRECTORY);
             return get(FessConfig.LDAP_ATTR_HOME_DIRECTORY);
         }
         }
 
 
-        public String getSsoType() {
-            return get(FessConfig.SSO_TYPE);
-        }
-
-        public String getSpnegoLoggerLevel() {
-            return get(FessConfig.SPNEGO_LOGGER_LEVEL);
-        }
-
-        public Integer getSpnegoLoggerLevelAsInteger() {
-            return getAsInteger(FessConfig.SPNEGO_LOGGER_LEVEL);
-        }
-
-        public String getSpnegoKrb5Conf() {
-            return get(FessConfig.SPNEGO_KRB5_CONF);
-        }
-
-        public String getSpnegoLoginConf() {
-            return get(FessConfig.SPNEGO_LOGIN_CONF);
-        }
-
-        public String getSpnegoPreauthUsername() {
-            return get(FessConfig.SPNEGO_PREAUTH_USERNAME);
-        }
-
-        public String getSpnegoPreauthPassword() {
-            return get(FessConfig.SPNEGO_PREAUTH_PASSWORD);
-        }
-
-        public String getSpnegoLoginClientModule() {
-            return get(FessConfig.SPNEGO_LOGIN_CLIENT_MODULE);
-        }
-
-        public String getSpnegoLoginServerModule() {
-            return get(FessConfig.SPNEGO_LOGIN_SERVER_MODULE);
-        }
-
-        public String getSpnegoAllowBasic() {
-            return get(FessConfig.SPNEGO_ALLOW_BASIC);
-        }
-
-        public boolean isSpnegoAllowBasic() {
-            return is(FessConfig.SPNEGO_ALLOW_BASIC);
-        }
-
-        public String getSpnegoAllowUnsecureBasic() {
-            return get(FessConfig.SPNEGO_ALLOW_UNSECURE_BASIC);
-        }
-
-        public boolean isSpnegoAllowUnsecureBasic() {
-            return is(FessConfig.SPNEGO_ALLOW_UNSECURE_BASIC);
-        }
-
-        public String getSpnegoPromptNtlm() {
-            return get(FessConfig.SPNEGO_PROMPT_NTLM);
-        }
-
-        public boolean isSpnegoPromptNtlm() {
-            return is(FessConfig.SPNEGO_PROMPT_NTLM);
-        }
-
-        public String getSpnegoAllowLocalhost() {
-            return get(FessConfig.SPNEGO_ALLOW_LOCALHOST);
-        }
-
-        public boolean isSpnegoAllowLocalhost() {
-            return is(FessConfig.SPNEGO_ALLOW_LOCALHOST);
-        }
-
-        public String getSpnegoAllowDelegation() {
-            return get(FessConfig.SPNEGO_ALLOW_DELEGATION);
-        }
-
-        public boolean isSpnegoAllowDelegation() {
-            return is(FessConfig.SPNEGO_ALLOW_DELEGATION);
-        }
-
-        public String getSpnegoExcludeDirs() {
-            return get(FessConfig.SPNEGO_EXCLUDE_DIRS);
-        }
-
-        public Integer getSpnegoExcludeDirsAsInteger() {
-            return getAsInteger(FessConfig.SPNEGO_EXCLUDE_DIRS);
-        }
-
-        public String getOicClientId() {
-            return get(FessConfig.OIC_CLIENT_ID);
-        }
-
-        public String getOicClientSecret() {
-            return get(FessConfig.OIC_CLIENT_SECRET);
-        }
-
-        public String getOicAuthServerUrl() {
-            return get(FessConfig.OIC_AUTH_SERVER_URL);
-        }
-
-        public String getOicRedirectUrl() {
-            return get(FessConfig.OIC_REDIRECT_URL);
-        }
-
-        public String getOicScope() {
-            return get(FessConfig.OIC_SCOPE);
-        }
-
-        public String getOicTokenServerUrl() {
-            return get(FessConfig.OIC_TOKEN_SERVER_URL);
-        }
-
-        public String getOicDefaultRoles() {
-            return get(FessConfig.OIC_DEFAULT_ROLES);
-        }
-
-        public String getOicDefaultGroups() {
-            return get(FessConfig.OIC_DEFAULT_GROUPS);
-        }
-
-        public Integer getOicDefaultGroupsAsInteger() {
-            return getAsInteger(FessConfig.OIC_DEFAULT_GROUPS);
-        }
-
         @Override
         @Override
         protected java.util.Map<String, String> prepareGeneratedDefaultMap() {
         protected java.util.Map<String, String> prepareGeneratedDefaultMap() {
             java.util.Map<String, String> defaultMap = super.prepareGeneratedDefaultMap();
             java.util.Map<String, String> defaultMap = super.prepareGeneratedDefaultMap();
@@ -9018,28 +8618,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.LDAP_ATTR_UID_NUMBER, "uidNumber");
             defaultMap.put(FessConfig.LDAP_ATTR_UID_NUMBER, "uidNumber");
             defaultMap.put(FessConfig.LDAP_ATTR_GID_NUMBER, "gidNumber");
             defaultMap.put(FessConfig.LDAP_ATTR_GID_NUMBER, "gidNumber");
             defaultMap.put(FessConfig.LDAP_ATTR_HOME_DIRECTORY, "homeDirectory");
             defaultMap.put(FessConfig.LDAP_ATTR_HOME_DIRECTORY, "homeDirectory");
-            defaultMap.put(FessConfig.SSO_TYPE, "none");
-            defaultMap.put(FessConfig.SPNEGO_LOGGER_LEVEL, "");
-            defaultMap.put(FessConfig.SPNEGO_KRB5_CONF, "krb5.conf");
-            defaultMap.put(FessConfig.SPNEGO_LOGIN_CONF, "auth_login.conf");
-            defaultMap.put(FessConfig.SPNEGO_PREAUTH_USERNAME, "username");
-            defaultMap.put(FessConfig.SPNEGO_PREAUTH_PASSWORD, "password");
-            defaultMap.put(FessConfig.SPNEGO_LOGIN_CLIENT_MODULE, "spnego-client");
-            defaultMap.put(FessConfig.SPNEGO_LOGIN_SERVER_MODULE, "spnego-server");
-            defaultMap.put(FessConfig.SPNEGO_ALLOW_BASIC, "true");
-            defaultMap.put(FessConfig.SPNEGO_ALLOW_UNSECURE_BASIC, "true");
-            defaultMap.put(FessConfig.SPNEGO_PROMPT_NTLM, "true");
-            defaultMap.put(FessConfig.SPNEGO_ALLOW_LOCALHOST, "true");
-            defaultMap.put(FessConfig.SPNEGO_ALLOW_DELEGATION, "false");
-            defaultMap.put(FessConfig.SPNEGO_EXCLUDE_DIRS, "");
-            defaultMap.put(FessConfig.OIC_CLIENT_ID, "__CLIENT_ID__");
-            defaultMap.put(FessConfig.OIC_CLIENT_SECRET, "__CLIENT_SECRET__");
-            defaultMap.put(FessConfig.OIC_AUTH_SERVER_URL, "https://accounts.google.com/o/oauth2/auth");
-            defaultMap.put(FessConfig.OIC_REDIRECT_URL, "http://localhost:8080/sso/");
-            defaultMap.put(FessConfig.OIC_SCOPE, "openid email");
-            defaultMap.put(FessConfig.OIC_TOKEN_SERVER_URL, "https://accounts.google.com/o/oauth2/token");
-            defaultMap.put(FessConfig.OIC_DEFAULT_ROLES, "guest");
-            defaultMap.put(FessConfig.OIC_DEFAULT_GROUPS, "");
             defaultMap.put(FessConfig.lasta_di_SMART_DEPLOY_MODE, "hot");
             defaultMap.put(FessConfig.lasta_di_SMART_DEPLOY_MODE, "hot");
             defaultMap.put(FessConfig.DEVELOPMENT_HERE, "true");
             defaultMap.put(FessConfig.DEVELOPMENT_HERE, "true");
             defaultMap.put(FessConfig.ENVIRONMENT_TITLE, "Local Development");
             defaultMap.put(FessConfig.ENVIRONMENT_TITLE, "Local Development");

+ 9 - 11
src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java

@@ -1203,16 +1203,15 @@ public interface FessProp {
         return list;
         return list;
     }
     }
 
 
-    String getOicDefaultGroups();
-
     default String[] getOicDefaultGroupsAsArray() {
     default String[] getOicDefaultGroupsAsArray() {
         String[] array = (String[]) propMap.get(OIC_DEFAULT_GROUPS);
         String[] array = (String[]) propMap.get(OIC_DEFAULT_GROUPS);
         if (array == null) {
         if (array == null) {
-            if (StringUtil.isBlank(getOicDefaultGroups())) {
+            final String oicDefaultGroups = getSystemProperty("oic.default.groups");
+            if (StringUtil.isBlank(oicDefaultGroups)) {
                 array = StringUtil.EMPTY_STRINGS;
                 array = StringUtil.EMPTY_STRINGS;
             } else {
             } else {
                 array =
                 array =
-                        split(getOicDefaultGroups(), ",").get(
+                        split(oicDefaultGroups, ",").get(
                                 stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
                                 stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
             }
             }
             propMap.put(OIC_DEFAULT_GROUPS, array);
             propMap.put(OIC_DEFAULT_GROUPS, array);
@@ -1220,16 +1219,15 @@ public interface FessProp {
         return array;
         return array;
     }
     }
 
 
-    String getOicDefaultRoles();
-
     default String[] getOicDefaultRolesAsArray() {
     default String[] getOicDefaultRolesAsArray() {
         String[] array = (String[]) propMap.get(OIC_DEFAULT_ROLES);
         String[] array = (String[]) propMap.get(OIC_DEFAULT_ROLES);
         if (array == null) {
         if (array == null) {
-            if (StringUtil.isBlank(getOicDefaultRoles())) {
+            final String oicDefaultRoles = getSystemProperty("oic.default.roles");
+            if (StringUtil.isBlank(oicDefaultRoles)) {
                 array = StringUtil.EMPTY_STRINGS;
                 array = StringUtil.EMPTY_STRINGS;
             } else {
             } else {
                 array =
                 array =
-                        split(getOicDefaultRoles(), ",").get(
+                        split(oicDefaultRoles, ",").get(
                                 stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
                                 stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
             }
             }
             propMap.put(OIC_DEFAULT_ROLES, array);
             propMap.put(OIC_DEFAULT_ROLES, array);
@@ -1927,7 +1925,7 @@ public interface FessProp {
         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_GSA_RESPONSE_HEADER_LIST);
         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_GSA_RESPONSE_HEADER_LIST);
         if (list == null) {
         if (list == null) {
             list = split(getApiGsaResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
             list = split(getApiGsaResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
-                String[] values = s.split(":", 2);
+                final String[] values = s.split(":", 2);
                 if (values.length == 2) {
                 if (values.length == 2) {
                     return new Pair<>(values[0], values[1]);
                     return new Pair<>(values[0], values[1]);
                 }
                 }
@@ -1944,7 +1942,7 @@ public interface FessProp {
         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_JSON_RESPONSE_HEADER_LIST);
         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_JSON_RESPONSE_HEADER_LIST);
         if (list == null) {
         if (list == null) {
             list = split(getApiJsonResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
             list = split(getApiJsonResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
-                String[] values = s.split(":", 2);
+                final String[] values = s.split(":", 2);
                 if (values.length == 2) {
                 if (values.length == 2) {
                     return new Pair<>(values[0], values[1]);
                     return new Pair<>(values[0], values[1]);
                 }
                 }
@@ -1961,7 +1959,7 @@ public interface FessProp {
         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_DASHBOARD_RESPONSE_HEADER_LIST);
         List<Pair<String, String>> list = (List<Pair<String, String>>) propMap.get(API_DASHBOARD_RESPONSE_HEADER_LIST);
         if (list == null) {
         if (list == null) {
             list = split(getApiDashboardResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
             list = split(getApiDashboardResponseHeaders(), "\n").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> {
-                String[] values = s.split(":", 2);
+                final String[] values = s.split(":", 2);
                 if (values.length == 2) {
                 if (values.length == 2) {
                     return new Pair<>(values[0], values[1]);
                     return new Pair<>(values[0], values[1]);
                 }
                 }

+ 3 - 0
src/main/java/org/codelibs/fess/sso/SsoAuthenticator.java

@@ -15,10 +15,13 @@
  */
  */
 package org.codelibs.fess.sso;
 package org.codelibs.fess.sso;
 
 
+import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
 
 
 public interface SsoAuthenticator {
 public interface SsoAuthenticator {
 
 
     LoginCredential getLoginCredential();
     LoginCredential getLoginCredential();
 
 
+    void resolveCredential(LoginCredentialResolver resolver);
+
 }
 }

+ 27 - 11
src/main/java/org/codelibs/fess/sso/SsoManager.java

@@ -15,31 +15,47 @@
  */
  */
 package org.codelibs.fess.sso;
 package org.codelibs.fess.sso;
 
 
-import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.List;
 
 
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ComponentUtil;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 
 public class SsoManager {
 public class SsoManager {
+    private static final Logger logger = LoggerFactory.getLogger(SsoManager.class);
 
 
-    private SsoAuthenticator authenticator;
+    protected static final String SSO_TYPE = "sso.type";
 
 
-    @PostConstruct
-    public void init() {
-        final String ssoType = ComponentUtil.getFessConfig().getSsoType();
-        if (!"none".equals(ssoType)) {
-            authenticator = ComponentUtil.getComponent(ssoType + "Authenticator");
-        }
-    }
+    protected static final String NONE = "none";
+
+    protected final List<SsoAuthenticator> authenticatorList = new ArrayList<>();
 
 
     public boolean available() {
     public boolean available() {
-        return authenticator != null;
+        return !NONE.equals(getSsoType());
     }
     }
 
 
     public LoginCredential getLoginCredential() {
     public LoginCredential getLoginCredential() {
-        if (authenticator != null) {
+        if (available()) {
+            final SsoAuthenticator authenticator = ComponentUtil.getComponent(getSsoType() + "Authenticator");
             return authenticator.getLoginCredential();
             return authenticator.getLoginCredential();
         }
         }
         return null;
         return null;
     }
     }
+
+    protected String getSsoType() {
+        return ComponentUtil.getFessConfig().getSystemProperty(SSO_TYPE, NONE);
+    }
+
+    public SsoAuthenticator[] getAuthenticators() {
+        return authenticatorList.toArray(new SsoAuthenticator[authenticatorList.size()]);
+    }
+
+    public void register(final SsoAuthenticator authenticator) {
+        if (logger.isInfoEnabled()) {
+            logger.info("Load " + authenticator.getClass().getSimpleName());
+        }
+        authenticatorList.add(authenticator);
+    }
 }
 }

+ 305 - 0
src/main/java/org/codelibs/fess/sso/aad/AzureAdAuthenticator.java

@@ -0,0 +1,305 @@
+/*
+ * Copyright 2012-2019 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.sso.aad;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.lang3.StringUtils;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.core.net.UuidUtil;
+import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
+import org.codelibs.fess.app.web.base.login.AzureAdCredential;
+import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
+import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
+import org.codelibs.fess.crawler.Constants;
+import org.codelibs.fess.exception.SsoLoginException;
+import org.codelibs.fess.sso.SsoAuthenticator;
+import org.codelibs.fess.util.ComponentUtil;
+import org.dbflute.optional.OptionalEntity;
+import org.lastaflute.web.login.credential.LoginCredential;
+import org.lastaflute.web.response.HtmlResponse;
+import org.lastaflute.web.util.LaRequestUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.microsoft.aad.adal4j.AuthenticationContext;
+import com.microsoft.aad.adal4j.AuthenticationResult;
+import com.microsoft.aad.adal4j.ClientCredential;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+public class AzureAdAuthenticator implements SsoAuthenticator {
+
+    private static final Logger logger = LoggerFactory.getLogger(AzureAdAuthenticator.class);
+
+    protected static final String AZUREAD_STATE_TTL = "azuread.state.ttl";
+
+    protected static final String AZUREAD_AUTHORITY = "azuread.authority";
+
+    protected static final String AZUREAD_TENANT = "azuread.tenant";
+
+    protected static final String AZUREAD_CLIENT_SECRET = "azuread.client.secret";
+
+    protected static final String AZUREAD_CLIENT_ID = "azuread.client.id";
+
+    protected static final String STATES = "aadStates";
+
+    protected static final String STATE = "aadState";
+
+    protected static final String ERROR = "error";
+
+    protected static final String ERROR_DESCRIPTION = "error_description";
+
+    protected static final String ERROR_URI = "error_uri";
+
+    protected static final String ID_TOKEN = "id_token";
+
+    protected static final String CODE = "code";
+
+    @PostConstruct
+    public void init() {
+        ComponentUtil.getSsoManager().register(this);
+    }
+
+    @Override
+    public LoginCredential getLoginCredential() {
+        return LaRequestUtil.getOptionalRequest().map(request -> {
+            final HttpSession session = request.getSession(false);
+            if (session != null && containsAuthenticationData(request)) {
+                try {
+                    return processAuthenticationData(request);
+                } catch (final Exception e) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Failed to process a login request on AzureAD.", e);
+                    }
+                }
+                return null;
+            }
+
+            // TODO refresh
+
+                return new ActionResponseCredential(() -> HtmlResponse.fromRedirectPathAsIs(getAuthUrl(request)));
+            }).orElse(null);
+    }
+
+    protected String getAuthUrl(final HttpServletRequest request) {
+        final String state = UuidUtil.create();
+        final String nonce = UuidUtil.create();
+        storeStateInSession(request.getSession(), state, nonce);
+        return getAuthority() + getTenant()
+                + "/oauth2/authorize?response_type=code&scope=directory.read.all&response_mode=form_post&redirect_uri="
+                + URLEncoder.encode(request.getRequestURL().toString(), Constants.UTF_8_CHARSET) + "&client_id=" + getClientId()
+                + "&resource=https%3a%2f%2fgraph.microsoft.com" + "&state=" + state + "&nonce=" + nonce;
+
+    }
+
+    protected void storeStateInSession(final HttpSession session, final String state, final String nonce) {
+        @SuppressWarnings("unchecked")
+        Map<String, StateData> stateMap = (Map<String, StateData>) session.getAttribute(STATES);
+        if (stateMap == null) {
+            stateMap = new HashMap<>();
+            session.setAttribute(STATES, stateMap);
+        }
+        stateMap.put(state, new StateData(nonce, System.currentTimeMillis()));
+    }
+
+    protected LoginCredential processAuthenticationData(final HttpServletRequest request) {
+        final StringBuffer urlBuf = request.getRequestURL();
+        final String queryStr = request.getQueryString();
+        if (queryStr != null) {
+            urlBuf.append('?').append(queryStr);
+        }
+
+        final Map<String, String> params = new HashMap<>();
+        for (final Map.Entry<String, String[]> e : request.getParameterMap().entrySet()) {
+            if (e.getValue().length > 0) {
+                params.put(e.getKey(), e.getValue()[0]);
+            }
+        }
+
+        // validate that state in response equals to state in request
+        final StateData stateData = validateState(request.getSession(), params.get(STATE));
+
+        final AuthenticationResponse authResponse = parseAuthenticationResponse(urlBuf.toString(), params);
+        if (authResponse instanceof AuthenticationSuccessResponse) {
+            final AuthenticationSuccessResponse oidcResponse = (AuthenticationSuccessResponse) authResponse;
+            // validate that OIDC Auth Response matches Code Flow (contains only requested artifacts)
+            validateAuthRespMatchesCodeFlow(oidcResponse);
+
+            final AuthenticationResult authData = getAccessToken(oidcResponse.getAuthorizationCode(), request.getRequestURL().toString());
+            // validate nonce to prevent reply attacks (code maybe substituted to one with broader access)
+
+            validateNonce(stateData, authData);
+
+            return new AzureAdCredential(authData);
+        } else {
+            final AuthenticationErrorResponse oidcResponse = (AuthenticationErrorResponse) authResponse;
+            throw new SsoLoginException(String.format("Request for auth code failed: %s - %s", oidcResponse.getErrorObject().getCode(),
+                    oidcResponse.getErrorObject().getDescription()));
+        }
+    }
+
+    protected AuthenticationResponse parseAuthenticationResponse(final String url, final Map<String, String> params) {
+        try {
+            return AuthenticationResponseParser.parse(new URI(url), params);
+        } catch (final Exception e) {
+            throw new SsoLoginException("Failed to parse an authentication response.", e);
+        }
+    }
+
+    protected void validateNonce(final StateData stateData, final AuthenticationResult authData) {
+        final String idToken = authData.getIdToken();
+        try {
+            final JWTClaimsSet claimsSet = JWTParser.parse(idToken).getJWTClaimsSet();
+            if (claimsSet == null) {
+                throw new SsoLoginException("could not validate nonce");
+            }
+
+            final String nonce = (String) claimsSet.getClaim("nonce");
+            if (StringUtils.isEmpty(nonce) || !nonce.equals(stateData.getNonce())) {
+                throw new SsoLoginException("could not validate nonce");
+            }
+        } catch (final SsoLoginException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SsoLoginException("could not validate nonce", e);
+        }
+    }
+
+    protected AuthenticationResult getAccessToken(final AuthorizationCode authorizationCode, final String currentUri) {
+        final String authCode = authorizationCode.getValue();
+        final ClientCredential credential = new ClientCredential(getClientId(), getClientSecret());
+        ExecutorService service = null;
+        try {
+            service = Executors.newFixedThreadPool(1);// TODO replace with something...
+            final AuthenticationContext context = new AuthenticationContext(getAuthority() + getTenant() + "/", true, service);
+            final Future<AuthenticationResult> future =
+                    context.acquireTokenByAuthorizationCode(authCode, new URI(currentUri), credential, null);
+            final AuthenticationResult result = future.get();
+            if (result == null) {
+                throw new SsoLoginException("authentication result was null");
+            }
+            return result;
+        } catch (final Exception e) {
+            throw new SsoLoginException("Failed to get a token.", e);
+        } finally {
+            if (service != null) {
+                service.shutdown();
+            }
+        }
+    }
+
+    protected void validateAuthRespMatchesCodeFlow(final AuthenticationSuccessResponse oidcResponse) {
+        if (oidcResponse.getIDToken() != null || oidcResponse.getAccessToken() != null || oidcResponse.getAuthorizationCode() == null) {
+            throw new SsoLoginException("unexpected set of artifacts received");
+        }
+    }
+
+    protected StateData validateState(final HttpSession session, final String state) {
+        if (StringUtils.isNotEmpty(state)) {
+            final StateData stateDataInSession = removeStateFromSession(session, state);
+            if (stateDataInSession != null) {
+                return stateDataInSession;
+            }
+        }
+        throw new SsoLoginException("could not validate state");
+    }
+
+    protected StateData removeStateFromSession(final HttpSession session, final String state) {
+        @SuppressWarnings("unchecked")
+        final Map<String, StateData> states = (Map<String, StateData>) session.getAttribute(STATES);
+        if (states != null) {
+            final long now = System.currentTimeMillis();
+            states.entrySet().stream().filter(e -> (now - e.getValue().getExpiration()) / 1000L > getStateTtl()).map(Map.Entry::getKey)
+                    .collect(Collectors.toList()).forEach(s -> states.remove(s));
+            final StateData stateData = states.get(state);
+            if (stateData != null) {
+                states.remove(state);
+                return stateData;
+            }
+        }
+        return null;
+    }
+
+    protected boolean containsAuthenticationData(final HttpServletRequest request) {
+        if (!request.getMethod().equalsIgnoreCase("POST")) {
+            return false;
+        }
+        final Map<String, String[]> params = request.getParameterMap();
+        return params.containsKey(ERROR) || params.containsKey(ID_TOKEN) || params.containsKey(CODE);
+    }
+
+    protected class StateData {
+        private final String nonce;
+        private final long expiration;
+
+        public StateData(final String nonce, final long expiration) {
+            this.nonce = nonce;
+            this.expiration = expiration;
+        }
+
+        public String getNonce() {
+            return nonce;
+        }
+
+        public long getExpiration() {
+            return expiration;
+        }
+    }
+
+    protected String getClientId() {
+        return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_CLIENT_ID, StringUtil.EMPTY);
+    }
+
+    protected String getClientSecret() {
+        return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_CLIENT_SECRET, StringUtil.EMPTY);
+    }
+
+    protected String getTenant() {
+        return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_TENANT, StringUtil.EMPTY);
+    }
+
+    protected String getAuthority() {
+        return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_AUTHORITY, StringUtil.EMPTY);
+    }
+
+    protected long getStateTtl() {
+        return Long.parseLong(ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_STATE_TTL, "3600"));
+    }
+
+    @Override
+    public void resolveCredential(final LoginCredentialResolver resolver) {
+        resolver.resolve(OpenIdConnectCredential.class, credential -> {
+            return OptionalEntity.of(credential.getUser());
+        });
+    }
+}

+ 61 - 13
src/main/java/org/codelibs/fess/sso/oic/OpenIdConnectAuthenticator.java

@@ -20,17 +20,19 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 
 
+import javax.annotation.PostConstruct;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import javax.servlet.http.HttpSession;
 
 
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.net.UuidUtil;
 import org.codelibs.core.net.UuidUtil;
 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
+import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
 import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
 import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
 import org.codelibs.fess.crawler.Constants;
 import org.codelibs.fess.crawler.Constants;
-import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.sso.SsoAuthenticator;
 import org.codelibs.fess.sso.SsoAuthenticator;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ComponentUtil;
+import org.dbflute.optional.OptionalEntity;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.util.LaRequestUtil;
 import org.lastaflute.web.util.LaRequestUtil;
@@ -53,11 +55,28 @@ public class OpenIdConnectAuthenticator implements SsoAuthenticator {
 
 
     private static final Logger logger = LoggerFactory.getLogger(OpenIdConnectAuthenticator.class);
     private static final Logger logger = LoggerFactory.getLogger(OpenIdConnectAuthenticator.class);
 
 
-    private static final String OIC_STATE = "OIC_STATE";
+    protected static final String OIC_AUTH_SERVER_URL = "oic.auth.server.url";
 
 
-    private final HttpTransport httpTransport = new NetHttpTransport();
+    protected static final String OIC_CLIENT_ID = "oic.client.id";
 
 
-    private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
+    protected static final String OIC_SCOPE = "oic.scope";
+
+    protected static final String OIC_REDIRECT_URL = "oic.redirect.url";
+
+    protected static final String OIC_TOKEN_SERVER_URL = "oic.token.server.url";
+
+    protected static final String OIC_CLIENT_SECRET = "oic.client.secret";
+
+    protected static final String OIC_STATE = "OIC_STATE";
+
+    protected final HttpTransport httpTransport = new NetHttpTransport();
+
+    protected final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
+
+    @PostConstruct
+    public void init() {
+        ComponentUtil.getSsoManager().register(this);
+    }
 
 
     @Override
     @Override
     public LoginCredential getLoginCredential() {
     public LoginCredential getLoginCredential() {
@@ -84,13 +103,12 @@ public class OpenIdConnectAuthenticator implements SsoAuthenticator {
     }
     }
 
 
     protected String getAuthUrl(final HttpServletRequest request) {
     protected String getAuthUrl(final HttpServletRequest request) {
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final String state = UuidUtil.create();
         final String state = UuidUtil.create();
         request.getSession().setAttribute(OIC_STATE, state);
         request.getSession().setAttribute(OIC_STATE, state);
-        return new AuthorizationCodeRequestUrl(fessConfig.getOicAuthServerUrl(), fessConfig.getOicClientId())//
-                .setScopes(Arrays.asList(fessConfig.getOicScope()))//
+        return new AuthorizationCodeRequestUrl(getOicAuthServerUrl(), getOicClientId())//
+                .setScopes(Arrays.asList(getOicScope()))//
                 .setResponseTypes(Arrays.asList("code"))//
                 .setResponseTypes(Arrays.asList("code"))//
-                .setRedirectUri(fessConfig.getOicRedirectUrl())//
+                .setRedirectUri(getOicRedirectUrl())//
                 .setState(state)//
                 .setState(state)//
                 .build();
                 .build();
     }
     }
@@ -174,12 +192,42 @@ public class OpenIdConnectAuthenticator implements SsoAuthenticator {
     }
     }
 
 
     protected TokenResponse getTokenUrl(final String code) throws IOException {
     protected TokenResponse getTokenUrl(final String code) throws IOException {
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        return new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(fessConfig.getOicTokenServerUrl()), code)//
+        return new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(getOicTokenServerUrl()), code)//
                 .setGrantType("authorization_code")//
                 .setGrantType("authorization_code")//
-                .setRedirectUri(fessConfig.getOicRedirectUrl())//
-                .set("client_id", fessConfig.getOicClientId())//
-                .set("client_secret", fessConfig.getOicClientSecret())//
+                .setRedirectUri(getOicRedirectUrl())//
+                .set("client_id", getOicClientId())//
+                .set("client_secret", getOicClientSecret())//
                 .execute();
                 .execute();
     }
     }
+
+    protected String getOicClientSecret() {
+        return ComponentUtil.getSystemProperties().getProperty(OIC_CLIENT_SECRET, StringUtil.EMPTY);
+    }
+
+    protected String getOicTokenServerUrl() {
+        return ComponentUtil.getSystemProperties().getProperty(OIC_TOKEN_SERVER_URL, "https://accounts.google.com/o/oauth2/token");
+    }
+
+    protected String getOicRedirectUrl() {
+        return ComponentUtil.getSystemProperties().getProperty(OIC_REDIRECT_URL, "http://localhost:8080/sso/");
+    }
+
+    protected String getOicScope() {
+        return ComponentUtil.getSystemProperties().getProperty(OIC_SCOPE, StringUtil.EMPTY);
+    }
+
+    protected String getOicClientId() {
+        return ComponentUtil.getSystemProperties().getProperty(OIC_CLIENT_ID, StringUtil.EMPTY);
+    }
+
+    protected String getOicAuthServerUrl() {
+        return ComponentUtil.getSystemProperties().getProperty(OIC_AUTH_SERVER_URL, "https://accounts.google.com/o/oauth2/auth");
+    }
+
+    @Override
+    public void resolveCredential(final LoginCredentialResolver resolver) {
+        resolver.resolve(OpenIdConnectCredential.class, credential -> {
+            return OptionalEntity.of(credential.getUser());
+        });
+    }
 }
 }

+ 59 - 27
src/main/java/org/codelibs/fess/sso/spnego/SpnegoAuthenticator.java

@@ -26,10 +26,9 @@ import javax.servlet.http.HttpServletResponse;
 import org.codelibs.core.io.ResourceUtil;
 import org.codelibs.core.io.ResourceUtil;
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
+import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
 import org.codelibs.fess.app.web.base.login.SpnegoCredential;
 import org.codelibs.fess.app.web.base.login.SpnegoCredential;
-import org.codelibs.fess.exception.FessSystemException;
 import org.codelibs.fess.exception.SsoLoginException;
 import org.codelibs.fess.exception.SsoLoginException;
-import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.sso.SsoAuthenticator;
 import org.codelibs.fess.sso.SsoAuthenticator;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.spnego.SpnegoFilterConfig;
 import org.codelibs.spnego.SpnegoFilterConfig;
@@ -37,6 +36,7 @@ import org.codelibs.spnego.SpnegoHttpFilter;
 import org.codelibs.spnego.SpnegoHttpFilter.Constants;
 import org.codelibs.spnego.SpnegoHttpFilter.Constants;
 import org.codelibs.spnego.SpnegoHttpServletResponse;
 import org.codelibs.spnego.SpnegoHttpServletResponse;
 import org.codelibs.spnego.SpnegoPrincipal;
 import org.codelibs.spnego.SpnegoPrincipal;
+import org.dbflute.optional.OptionalEntity;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.login.credential.LoginCredential;
 import org.lastaflute.web.servlet.filter.RequestLoggingFilter;
 import org.lastaflute.web.servlet.filter.RequestLoggingFilter;
 import org.lastaflute.web.util.LaRequestUtil;
 import org.lastaflute.web.util.LaRequestUtil;
@@ -47,19 +47,37 @@ import org.slf4j.LoggerFactory;
 public class SpnegoAuthenticator implements SsoAuthenticator {
 public class SpnegoAuthenticator implements SsoAuthenticator {
     private static final Logger logger = LoggerFactory.getLogger(SpnegoAuthenticator.class);
     private static final Logger logger = LoggerFactory.getLogger(SpnegoAuthenticator.class);
 
 
+    protected static final String SPNEGO_EXCLUDE_DIRS = "spnego.exclude.dirs";
+    protected static final String SPNEGO_ALLOW_DELEGATION = "spnego.allow.delegation";
+    protected static final String SPNEGO_ALLOW_LOCALHOST = "spnego.allow.localhost";
+    protected static final String SPNEGO_PROMPT_NTLM = "spnego.prompt.ntlm";
+    protected static final String SPNEGO_ALLOW_UNSECURE_BASIC = "spnego.allow.unsecure.basic";
+    protected static final String SPNEGO_ALLOW_BASIC = "spnego.allow.basic";
+    protected static final String SPNEGO_PREAUTH_PASSWORD = "spnego.preauth.password";
+    protected static final String SPNEGO_PREAUTH_USERNAME = "spnego.preauth.username";
+    protected static final String SPNEGO_LOGIN_SERVER_MODULE = "spnego.login.server.module";
+    protected static final String SPNEGO_LOGIN_CLIENT_MODULE = "spnego.login.client.module";
+    protected static final String SPNEGO_KRB5_CONF = "spnego.krb5.conf";
+    protected static final String SPNEGO_LOGIN_CONF = "spnego.login.conf";
+    protected static final String SPNEGO_LOGGER_LEVEL = "spnego.logger.level";
+
     protected org.codelibs.spnego.SpnegoAuthenticator authenticator = null;
     protected org.codelibs.spnego.SpnegoAuthenticator authenticator = null;
 
 
     @PostConstruct
     @PostConstruct
     public void init() {
     public void init() {
-        if ("spnego".equals(ComponentUtil.getFessConfig().getSsoType())) {
-            try {
-                // set some System properties
-                final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
-
-                // pre-authenticate
-                authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
-            } catch (final Exception e) {
-                throw new FessSystemException("Failed to initialize SPNEGO.", e);
+        try {
+            // set some System properties
+            final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
+
+            // pre-authenticate
+            authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
+
+            ComponentUtil.getSsoManager().register(this);
+        } catch (final Exception e) {
+            if (logger.isDebugEnabled()) {
+                logger.warn("Failed to initialize SPNEGO.", e);
+            } else {
+                logger.warn("Failed to initialize SPNEGO.");
             }
             }
         }
         }
     }
     }
@@ -116,8 +134,6 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
 
 
     protected class SpengoConfig implements FilterConfig {
     protected class SpengoConfig implements FilterConfig {
 
 
-        protected FessConfig fessConfig = ComponentUtil.getFessConfig();
-
         @Override
         @Override
         public String getFilterName() {
         public String getFilterName() {
             return SpnegoAuthenticator.class.getName();
             return SpnegoAuthenticator.class.getName();
@@ -131,8 +147,9 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
         @Override
         @Override
         public String getInitParameter(final String name) {
         public String getInitParameter(final String name) {
             if (SpnegoHttpFilter.Constants.LOGGER_LEVEL.equals(name)) {
             if (SpnegoHttpFilter.Constants.LOGGER_LEVEL.equals(name)) {
-                if (StringUtil.isNotBlank(fessConfig.getSpnegoLoggerLevel())) {
-                    return fessConfig.getSpnegoLoggerLevel();
+                final String logLevel = getProperty(SPNEGO_LOGGER_LEVEL, StringUtil.EMPTY);
+                if (StringUtil.isNotBlank(logLevel)) {
+                    return logLevel;
                 } else if (logger.isDebugEnabled()) {
                 } else if (logger.isDebugEnabled()) {
                     return "3";
                     return "3";
                 } else if (logger.isInfoEnabled()) {
                 } else if (logger.isInfoEnabled()) {
@@ -145,33 +162,37 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
                     return "0";
                     return "0";
                 }
                 }
             } else if (SpnegoHttpFilter.Constants.LOGIN_CONF.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.LOGIN_CONF.equals(name)) {
-                return getResourcePath(fessConfig.getSpnegoLoginConf());
+                return getResourcePath(getProperty(SPNEGO_LOGIN_CONF, "auth_login.conf"));
             } else if (SpnegoHttpFilter.Constants.KRB5_CONF.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.KRB5_CONF.equals(name)) {
-                return getResourcePath(fessConfig.getSpnegoKrb5Conf());
+                return getResourcePath(getProperty(SPNEGO_KRB5_CONF, "krb5.conf"));
             } else if (SpnegoHttpFilter.Constants.CLIENT_MODULE.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.CLIENT_MODULE.equals(name)) {
-                return fessConfig.getSpnegoLoginClientModule();
+                return getProperty(SPNEGO_LOGIN_CLIENT_MODULE, "spnego-client");
             } else if (SpnegoHttpFilter.Constants.SERVER_MODULE.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.SERVER_MODULE.equals(name)) {
-                return fessConfig.getSpnegoLoginServerModule();
+                return getProperty(SPNEGO_LOGIN_SERVER_MODULE, "spnego-server");
             } else if (SpnegoHttpFilter.Constants.PREAUTH_USERNAME.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.PREAUTH_USERNAME.equals(name)) {
-                return fessConfig.getSpnegoPreauthUsername();
+                return getProperty(SPNEGO_PREAUTH_USERNAME, "username");
             } else if (SpnegoHttpFilter.Constants.PREAUTH_PASSWORD.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.PREAUTH_PASSWORD.equals(name)) {
-                return fessConfig.getSpnegoPreauthPassword();
+                return getProperty(SPNEGO_PREAUTH_PASSWORD, "password");
             } else if (SpnegoHttpFilter.Constants.ALLOW_BASIC.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.ALLOW_BASIC.equals(name)) {
-                return fessConfig.getSpnegoAllowBasic();
+                return getProperty(SPNEGO_ALLOW_BASIC, "true");
             } else if (SpnegoHttpFilter.Constants.ALLOW_UNSEC_BASIC.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.ALLOW_UNSEC_BASIC.equals(name)) {
-                return fessConfig.getSpnegoAllowUnsecureBasic();
+                return getProperty(SPNEGO_ALLOW_UNSECURE_BASIC, "true");
             } else if (SpnegoHttpFilter.Constants.PROMPT_NTLM.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.PROMPT_NTLM.equals(name)) {
-                return fessConfig.getSpnegoPromptNtlm();
+                return getProperty(SPNEGO_PROMPT_NTLM, "true");
             } else if (SpnegoHttpFilter.Constants.ALLOW_LOCALHOST.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.ALLOW_LOCALHOST.equals(name)) {
-                return fessConfig.getSpnegoAllowLocalhost();
+                return getProperty(SPNEGO_ALLOW_LOCALHOST, "true");
             } else if (SpnegoHttpFilter.Constants.ALLOW_DELEGATION.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.ALLOW_DELEGATION.equals(name)) {
-                return fessConfig.getSpnegoAllowDelegation();
+                return getProperty(SPNEGO_ALLOW_DELEGATION, "false");
             } else if (SpnegoHttpFilter.Constants.EXCLUDE_DIRS.equals(name)) {
             } else if (SpnegoHttpFilter.Constants.EXCLUDE_DIRS.equals(name)) {
-                return fessConfig.getSpnegoExcludeDirs();
+                return getProperty(SPNEGO_EXCLUDE_DIRS, StringUtil.EMPTY);
             }
             }
             return null;
             return null;
         }
         }
 
 
+        protected String getProperty(final String key, final String defaultValue) {
+            return ComponentUtil.getSystemProperties().getProperty(key, defaultValue);
+        }
+
         protected String getResourcePath(final String path) {
         protected String getResourcePath(final String path) {
             final File file = ResourceUtil.getResourceAsFileNoException(path);
             final File file = ResourceUtil.getResourceAsFileNoException(path);
             if (file != null) {
             if (file != null) {
@@ -186,4 +207,15 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
         }
         }
 
 
     }
     }
+
+    @Override
+    public void resolveCredential(final LoginCredentialResolver resolver) {
+        resolver.resolve(SpnegoCredential.class, credential -> {
+            final String username = credential.getUserId();
+            if (!ComponentUtil.getFessConfig().isAdminUser(username)) {
+                return ComponentUtil.getLdapManager().login(username);
+            }
+            return OptionalEntity.empty();
+        });
+    }
 }
 }

+ 23 - 23
src/main/resources/fess_config.properties

@@ -686,26 +686,26 @@ ldap.attr.homeDirectory=homeDirectory
 # ----------------------------------------------------------
 # ----------------------------------------------------------
 #                                                      SSO
 #                                                      SSO
 #                                                     ------
 #                                                     ------
-sso.type=none
-spnego.logger.level=
-spnego.krb5.conf=krb5.conf
-spnego.login.conf=auth_login.conf
-spnego.preauth.username=username
-spnego.preauth.password=password
-spnego.login.client.module=spnego-client
-spnego.login.server.module=spnego-server
-spnego.allow.basic=true
-spnego.allow.unsecure.basic=true
-spnego.prompt.ntlm=true
-spnego.allow.localhost=true
-spnego.allow.delegation=false
-spnego.exclude.dirs=
-
-oic.client.id=__CLIENT_ID__
-oic.client.secret=__CLIENT_SECRET__
-oic.auth.server.url=https://accounts.google.com/o/oauth2/auth
-oic.redirect.url=http://localhost:8080/sso/
-oic.scope=openid email
-oic.token.server.url=https://accounts.google.com/o/oauth2/token
-oic.default.roles=guest
-oic.default.groups=
+#sso.type=none
+#spnego.logger.level=
+#spnego.krb5.conf=krb5.conf
+#spnego.login.conf=auth_login.conf
+#spnego.preauth.username=username
+#spnego.preauth.password=password
+#spnego.login.client.module=spnego-client
+#spnego.login.server.module=spnego-server
+#spnego.allow.basic=true
+#spnego.allow.unsecure.basic=true
+#spnego.prompt.ntlm=true
+#spnego.allow.localhost=true
+#spnego.allow.delegation=false
+#spnego.exclude.dirs=
+
+#oic.client.id=__CLIENT_ID__
+#oic.client.secret=__CLIENT_SECRET__
+#oic.auth.server.url=https://accounts.google.com/o/oauth2/auth
+#oic.redirect.url=http://localhost:8080/sso/
+#oic.scope=openid email
+#oic.token.server.url=https://accounts.google.com/o/oauth2/token
+#oic.default.roles=guest
+#oic.default.groups=

+ 11 - 0
src/main/resources/fess_sso++.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE components PUBLIC "-//DBFLUTE//DTD LastaDi 1.0//EN"
+	"http://dbflute.org/meta/lastadi10.dtd">
+<components>
+	<component name="aadAuthenticator" class="org.codelibs.fess.sso.aad.AzureAdAuthenticator">
+	</component>
+	<component name="oicAuthenticator" class="org.codelibs.fess.sso.oic.OpenIdConnectAuthenticator">
+	</component>
+	<component name="spnegoAuthenticator" class="org.codelibs.fess.sso.spnego.SpnegoAuthenticator">
+	</component>
+</components>

+ 0 - 6
src/main/resources/fess_sso.xml

@@ -4,10 +4,4 @@
 <components>
 <components>
 	<component name="ssoManager" class="org.codelibs.fess.sso.SsoManager">
 	<component name="ssoManager" class="org.codelibs.fess.sso.SsoManager">
 	</component>
 	</component>
-
-	<component name="spnegoAuthenticator" class="org.codelibs.fess.sso.spnego.SpnegoAuthenticator">
-	</component>
-	<component name="oicAuthenticator" class="org.codelibs.fess.sso.oic.OpenIdConnectAuthenticator">
-	</component>
-
 </components>
 </components>