瀏覽代碼

fix #1633 refactoring

Shinsuke Sugaya 6 年之前
父節點
當前提交
df3b2598e0

+ 21 - 1
src/main/java/org/codelibs/fess/app/web/base/login/AzureAdCredential.java

@@ -15,11 +15,13 @@
  */
  */
 package org.codelibs.fess.app.web.base.login;
 package org.codelibs.fess.app.web.base.login;
 
 
+import static org.codelibs.core.stream.StreamUtil.split;
 import static org.codelibs.core.stream.StreamUtil.stream;
 import static org.codelibs.core.stream.StreamUtil.stream;
 
 
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.Set;
 
 
+import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.entity.FessUser;
 import org.codelibs.fess.entity.FessUser;
 import org.codelibs.fess.helper.SystemHelper;
 import org.codelibs.fess.helper.SystemHelper;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.ComponentUtil;
@@ -46,7 +48,25 @@ public class AzureAdCredential implements LoginCredential, FessCredential {
     }
     }
 
 
     public User getUser() {
     public User getUser() {
-        return new User(authResult.getUserInfo().getDisplayableId(), new String[0], new String[0]);
+        return new User(authResult.getUserInfo().getDisplayableId(), getDefaultGroupsAsArray(), getDefaultRolesAsArray());
+    }
+
+    protected static String[] getDefaultGroupsAsArray() {
+        final String value = ComponentUtil.getFessConfig().getSystemProperty("azuread.default.groups");
+        if (StringUtil.isBlank(value)) {
+            return StringUtil.EMPTY_STRINGS;
+        } else {
+            return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
+        }
+    }
+
+    protected static String[] getDefaultRolesAsArray() {
+        final String value = ComponentUtil.getFessConfig().getSystemProperty("azuread.default.roles");
+        if (StringUtil.isBlank(value)) {
+            return StringUtil.EMPTY_STRINGS;
+        } else {
+            return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
+        }
     }
     }
 
 
     public static class User implements FessUser {
     public static class User implements FessUser {

+ 21 - 3
src/main/java/org/codelibs/fess/app/web/base/login/OpenIdConnectCredential.java

@@ -15,15 +15,16 @@
  */
  */
 package org.codelibs.fess.app.web.base.login;
 package org.codelibs.fess.app.web.base.login;
 
 
+import static org.codelibs.core.stream.StreamUtil.split;
 import static org.codelibs.core.stream.StreamUtil.stream;
 import static org.codelibs.core.stream.StreamUtil.stream;
 
 
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
+import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.entity.FessUser;
 import org.codelibs.fess.entity.FessUser;
 import org.codelibs.fess.helper.SystemHelper;
 import org.codelibs.fess.helper.SystemHelper;
-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;
 
 
@@ -46,8 +47,25 @@ public class OpenIdConnectCredential implements LoginCredential, FessCredential
     }
     }
 
 
     public User getUser() {
     public User getUser() {
-        final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        return new User(getUserId(), fessConfig.getOicDefaultGroupsAsArray(), fessConfig.getOicDefaultRolesAsArray());
+        return new User(getUserId(), getDefaultGroupsAsArray(), getDefaultRolesAsArray());
+    }
+
+    protected static String[] getDefaultGroupsAsArray() {
+        final String value = ComponentUtil.getFessConfig().getSystemProperty("oic.default.groups");
+        if (StringUtil.isBlank(value)) {
+            return StringUtil.EMPTY_STRINGS;
+        } else {
+            return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
+        }
+    }
+
+    protected static String[] getDefaultRolesAsArray() {
+        final String value = ComponentUtil.getFessConfig().getSystemProperty("oic.default.roles");
+        if (StringUtil.isBlank(value)) {
+            return StringUtil.EMPTY_STRINGS;
+        } else {
+            return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
+        }
     }
     }
 
 
     public static class User implements FessUser {
     public static class User implements FessUser {

+ 0 - 36
src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java

@@ -120,10 +120,6 @@ public interface FessProp {
 
 
     String INDEX_ADMIN_DOUBLE_FIELD_SET = "indexAdminDoubleFieldSet";
     String INDEX_ADMIN_DOUBLE_FIELD_SET = "indexAdminDoubleFieldSet";
 
 
-    String OIC_DEFAULT_ROLES = "oicDefaultRoles";
-
-    String OIC_DEFAULT_GROUPS = "oicDefaultGroups";
-
     String AUTHENTICATION_ADMIN_ROLES = "authenticationAdminRoles";
     String AUTHENTICATION_ADMIN_ROLES = "authenticationAdminRoles";
 
 
     String SEARCH_GUEST_PERMISSION_LIST = "searchGuestPermissionList";
     String SEARCH_GUEST_PERMISSION_LIST = "searchGuestPermissionList";
@@ -1203,38 +1199,6 @@ public interface FessProp {
         return list;
         return list;
     }
     }
 
 
-    default String[] getOicDefaultGroupsAsArray() {
-        String[] array = (String[]) propMap.get(OIC_DEFAULT_GROUPS);
-        if (array == null) {
-            final String oicDefaultGroups = getSystemProperty("oic.default.groups");
-            if (StringUtil.isBlank(oicDefaultGroups)) {
-                array = StringUtil.EMPTY_STRINGS;
-            } else {
-                array =
-                        split(oicDefaultGroups, ",").get(
-                                stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
-            }
-            propMap.put(OIC_DEFAULT_GROUPS, array);
-        }
-        return array;
-    }
-
-    default String[] getOicDefaultRolesAsArray() {
-        String[] array = (String[]) propMap.get(OIC_DEFAULT_ROLES);
-        if (array == null) {
-            final String oicDefaultRoles = getSystemProperty("oic.default.roles");
-            if (StringUtil.isBlank(oicDefaultRoles)) {
-                array = StringUtil.EMPTY_STRINGS;
-            } else {
-                array =
-                        split(oicDefaultRoles, ",").get(
-                                stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
-            }
-            propMap.put(OIC_DEFAULT_ROLES, array);
-        }
-        return array;
-    }
-
     String getIndexAdminArrayFields();
     String getIndexAdminArrayFields();
 
 
     default Set<String> getIndexAdminArrayFieldSet() {
     default Set<String> getIndexAdminArrayFieldSet() {

+ 51 - 15
src/main/java/org/codelibs/fess/sso/aad/AzureAdAuthenticator.java

@@ -34,7 +34,6 @@ 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.AzureAdCredential;
 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.FessLoginAssist.LoginCredentialResolver;
-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.exception.SsoLoginException;
 import org.codelibs.fess.exception.SsoLoginException;
 import org.codelibs.fess.sso.SsoAuthenticator;
 import org.codelibs.fess.sso.SsoAuthenticator;
@@ -73,7 +72,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
 
 
     protected static final String STATES = "aadStates";
     protected static final String STATES = "aadStates";
 
 
-    protected static final String STATE = "aadState";
+    protected static final String STATE = "state";
 
 
     protected static final String ERROR = "error";
     protected static final String ERROR = "error";
 
 
@@ -115,10 +114,15 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
         final String state = UuidUtil.create();
         final String state = UuidUtil.create();
         final String nonce = UuidUtil.create();
         final String nonce = UuidUtil.create();
         storeStateInSession(request.getSession(), state, nonce);
         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;
+        final String authUrl =
+                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;
+        if (logger.isDebugEnabled()) {
+            logger.debug("redirect to: {}", authUrl);
+        }
+        return authUrl;
 
 
     }
     }
 
 
@@ -129,7 +133,11 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
             stateMap = new HashMap<>();
             stateMap = new HashMap<>();
             session.setAttribute(STATES, stateMap);
             session.setAttribute(STATES, stateMap);
         }
         }
-        stateMap.put(state, new StateData(nonce, System.currentTimeMillis()));
+        final StateData stateData = new StateData(nonce, System.currentTimeMillis());
+        if (logger.isDebugEnabled()) {
+            logger.debug("store {} in session", stateData);
+        }
+        stateMap.put(state, stateData);
     }
     }
 
 
     protected LoginCredential processAuthenticationData(final HttpServletRequest request) {
     protected LoginCredential processAuthenticationData(final HttpServletRequest request) {
@@ -145,19 +153,21 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
                 params.put(e.getKey(), e.getValue()[0]);
                 params.put(e.getKey(), e.getValue()[0]);
             }
             }
         }
         }
+        if (logger.isDebugEnabled()) {
+            logger.debug("process authentication: url: {}, params: {}", urlBuf, params);
+        }
 
 
         // validate that state in response equals to state in request
         // validate that state in response equals to state in request
         final StateData stateData = validateState(request.getSession(), params.get(STATE));
         final StateData stateData = validateState(request.getSession(), params.get(STATE));
+        if (logger.isDebugEnabled()) {
+            logger.debug("load {}", stateData);
+        }
 
 
         final AuthenticationResponse authResponse = parseAuthenticationResponse(urlBuf.toString(), params);
         final AuthenticationResponse authResponse = parseAuthenticationResponse(urlBuf.toString(), params);
         if (authResponse instanceof AuthenticationSuccessResponse) {
         if (authResponse instanceof AuthenticationSuccessResponse) {
             final AuthenticationSuccessResponse oidcResponse = (AuthenticationSuccessResponse) authResponse;
             final AuthenticationSuccessResponse oidcResponse = (AuthenticationSuccessResponse) authResponse;
-            // validate that OIDC Auth Response matches Code Flow (contains only requested artifacts)
             validateAuthRespMatchesCodeFlow(oidcResponse);
             validateAuthRespMatchesCodeFlow(oidcResponse);
-
             final AuthenticationResult authData = getAccessToken(oidcResponse.getAuthorizationCode(), request.getRequestURL().toString());
             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);
             validateNonce(stateData, authData);
 
 
             return new AzureAdCredential(authData);
             return new AzureAdCredential(authData);
@@ -169,6 +179,9 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
     }
     }
 
 
     protected AuthenticationResponse parseAuthenticationResponse(final String url, final Map<String, String> params) {
     protected AuthenticationResponse parseAuthenticationResponse(final String url, final Map<String, String> params) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Parse: {} : {}", url, params);
+        }
         try {
         try {
             return AuthenticationResponseParser.parse(new URI(url), params);
             return AuthenticationResponseParser.parse(new URI(url), params);
         } catch (final Exception e) {
         } catch (final Exception e) {
@@ -178,6 +191,9 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
 
 
     protected void validateNonce(final StateData stateData, final AuthenticationResult authData) {
     protected void validateNonce(final StateData stateData, final AuthenticationResult authData) {
         final String idToken = authData.getIdToken();
         final String idToken = authData.getIdToken();
+        if (logger.isDebugEnabled()) {
+            logger.debug("idToken: {}", idToken);
+        }
         try {
         try {
             final JWTClaimsSet claimsSet = JWTParser.parse(idToken).getJWTClaimsSet();
             final JWTClaimsSet claimsSet = JWTParser.parse(idToken).getJWTClaimsSet();
             if (claimsSet == null) {
             if (claimsSet == null) {
@@ -185,6 +201,9 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
             }
             }
 
 
             final String nonce = (String) claimsSet.getClaim("nonce");
             final String nonce = (String) claimsSet.getClaim("nonce");
+            if (logger.isDebugEnabled()) {
+                logger.debug("nonce: {}", nonce);
+            }
             if (StringUtils.isEmpty(nonce) || !nonce.equals(stateData.getNonce())) {
             if (StringUtils.isEmpty(nonce) || !nonce.equals(stateData.getNonce())) {
                 throw new SsoLoginException("could not validate nonce");
                 throw new SsoLoginException("could not validate nonce");
             }
             }
@@ -196,12 +215,16 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
     }
     }
 
 
     protected AuthenticationResult getAccessToken(final AuthorizationCode authorizationCode, final String currentUri) {
     protected AuthenticationResult getAccessToken(final AuthorizationCode authorizationCode, final String currentUri) {
+        final String authority = getAuthority() + getTenant() + "/";
         final String authCode = authorizationCode.getValue();
         final String authCode = authorizationCode.getValue();
+        if (logger.isDebugEnabled()) {
+            logger.debug("authCode: {}, authority: {}, uri: {}", authCode, authority, currentUri);
+        }
         final ClientCredential credential = new ClientCredential(getClientId(), getClientSecret());
         final ClientCredential credential = new ClientCredential(getClientId(), getClientSecret());
         ExecutorService service = null;
         ExecutorService service = null;
         try {
         try {
             service = Executors.newFixedThreadPool(1);// TODO replace with something...
             service = Executors.newFixedThreadPool(1);// TODO replace with something...
-            final AuthenticationContext context = new AuthenticationContext(getAuthority() + getTenant() + "/", true, service);
+            final AuthenticationContext context = new AuthenticationContext(authority, true, service);
             final Future<AuthenticationResult> future =
             final Future<AuthenticationResult> future =
                     context.acquireTokenByAuthorizationCode(authCode, new URI(currentUri), credential, null);
                     context.acquireTokenByAuthorizationCode(authCode, new URI(currentUri), credential, null);
             final AuthenticationResult result = future.get();
             final AuthenticationResult result = future.get();
@@ -240,9 +263,17 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
         if (states != null) {
         if (states != null) {
             final long now = System.currentTimeMillis();
             final long now = System.currentTimeMillis();
             states.entrySet().stream().filter(e -> (now - e.getValue().getExpiration()) / 1000L > getStateTtl()).map(Map.Entry::getKey)
             states.entrySet().stream().filter(e -> (now - e.getValue().getExpiration()) / 1000L > getStateTtl()).map(Map.Entry::getKey)
-                    .collect(Collectors.toList()).forEach(s -> states.remove(s));
+                    .collect(Collectors.toList()).forEach(s -> {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("remove old state: {}", s);
+                        }
+                        states.remove(s);
+                    });
             final StateData stateData = states.get(state);
             final StateData stateData = states.get(state);
             if (stateData != null) {
             if (stateData != null) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("restore {} from session", stateData);
+                }
                 states.remove(state);
                 states.remove(state);
                 return stateData;
                 return stateData;
             }
             }
@@ -274,6 +305,11 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
         public long getExpiration() {
         public long getExpiration() {
             return expiration;
             return expiration;
         }
         }
+
+        @Override
+        public String toString() {
+            return "StateData [nonce=" + nonce + ", expiration=" + expiration + "]";
+        }
     }
     }
 
 
     protected String getClientId() {
     protected String getClientId() {
@@ -289,7 +325,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
     }
     }
 
 
     protected String getAuthority() {
     protected String getAuthority() {
-        return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_AUTHORITY, StringUtil.EMPTY);
+        return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_AUTHORITY, "https://login.microsoftonline.com/");
     }
     }
 
 
     protected long getStateTtl() {
     protected long getStateTtl() {
@@ -298,7 +334,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
 
 
     @Override
     @Override
     public void resolveCredential(final LoginCredentialResolver resolver) {
     public void resolveCredential(final LoginCredentialResolver resolver) {
-        resolver.resolve(OpenIdConnectCredential.class, credential -> {
+        resolver.resolve(AzureAdCredential.class, credential -> {
             return OptionalEntity.of(credential.getUser());
             return OptionalEntity.of(credential.getUser());
         });
         });
     }
     }

+ 16 - 7
src/main/java/org/codelibs/fess/sso/spnego/SpnegoAuthenticator.java

@@ -29,6 +29,7 @@ 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.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.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;
@@ -45,8 +46,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 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_INITIALIZED = "spnego.initialized";
     protected static final String SPNEGO_EXCLUDE_DIRS = "spnego.exclude.dirs";
     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_DELEGATION = "spnego.allow.delegation";
     protected static final String SPNEGO_ALLOW_LOCALHOST = "spnego.allow.localhost";
     protected static final String SPNEGO_ALLOW_LOCALHOST = "spnego.allow.localhost";
@@ -65,6 +68,14 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
 
 
     @PostConstruct
     @PostConstruct
     public void init() {
     public void init() {
+        ComponentUtil.getSsoManager().register(this);
+    }
+
+    protected synchronized org.codelibs.spnego.SpnegoAuthenticator getAuthenticator() {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (authenticator != null && fessConfig.getSystemPropertyAsBoolean(SPNEGO_INITIALIZED, false)) {
+            return authenticator;
+        }
         try {
         try {
             // set some System properties
             // set some System properties
             final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
             final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
@@ -72,13 +83,11 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
             // pre-authenticate
             // pre-authenticate
             authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
             authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
 
 
-            ComponentUtil.getSsoManager().register(this);
+            fessConfig.setSystemPropertyAsBoolean(SPNEGO_INITIALIZED, true);
+            fessConfig.storeSystemProperties();
+            return authenticator;
         } catch (final Exception e) {
         } catch (final Exception e) {
-            if (logger.isDebugEnabled()) {
-                logger.warn("Failed to initialize SPNEGO.", e);
-            } else {
-                logger.warn("Failed to initialize SPNEGO.");
-            }
+            throw new SsoLoginException("Failed to initialize SPNEGO.", e);
         }
         }
     }
     }
 
 
@@ -96,7 +105,7 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
                     // client/caller principal
                     // client/caller principal
                     final SpnegoPrincipal principal;
                     final SpnegoPrincipal principal;
                     try {
                     try {
-                        principal = authenticator.authenticate(request, spnegoResponse);
+                        principal = getAuthenticator().authenticate(request, spnegoResponse);
                     } catch (final Exception e) {
                     } catch (final Exception e) {
                         final String msg = "HTTP Authorization Header=" + request.getHeader(Constants.AUTHZ_HEADER);
                         final String msg = "HTTP Authorization Header=" + request.getHeader(Constants.AUTHZ_HEADER);
                         if (logger.isDebugEnabled()) {
                         if (logger.isDebugEnabled()) {