瀏覽代碼

#2469 refactoring

Shinsuke Sugaya 5 年之前
父節點
當前提交
d4a16f872e

+ 61 - 7
src/main/java/org/codelibs/fess/app/web/base/login/SamlCredential.java

@@ -1,9 +1,25 @@
+/*
+ * Copyright 2012-2020 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.split;
 import static org.codelibs.core.stream.StreamUtil.stream;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -52,7 +68,8 @@ public class SamlCredential implements LoginCredential, FessCredential {
     }
 
     public SamlUser getUser() {
-        return new SamlUser(getUserId(), getDefaultGroupsAsArray(), getDefaultRolesAsArray());
+        return new SamlUser(nameId, sessionIndex, nameIdFormat, nameidNameQualifier, nameidSPNameQualifier, getDefaultGroupsAsArray(),
+                getDefaultRolesAsArray());
     }
 
     protected String[] getDefaultGroupsAsArray() {
@@ -93,23 +110,36 @@ public class SamlCredential implements LoginCredential, FessCredential {
 
         private static final long serialVersionUID = 1L;
 
-        protected final String name;
-
         protected String[] groups;
 
         protected String[] roles;
 
         protected String[] permissions;
 
-        protected SamlUser(final String name, final String[] groups, final String[] roles) {
-            this.name = name;
+        protected String nameId;
+
+        protected String sessionIndex;
+
+        protected String nameIdFormat;
+
+        protected String nameidNameQualifier;
+
+        protected String nameidSPNameQualifier;
+
+        public SamlUser(final String nameId, final String sessionIndex, final String nameIdFormat, final String nameidNameQualifier,
+                final String nameidSPNameQualifier, final String[] groups, final String[] roles) {
+            this.nameId = nameId;
+            this.sessionIndex = sessionIndex;
+            this.nameIdFormat = nameIdFormat;
+            this.nameidNameQualifier = nameidNameQualifier;
+            this.nameidSPNameQualifier = nameidSPNameQualifier;
             this.groups = groups;
             this.roles = roles;
         }
 
         @Override
         public String getName() {
-            return name;
+            return nameId;
         }
 
         @Override
@@ -127,7 +157,7 @@ public class SamlCredential implements LoginCredential, FessCredential {
             if (permissions == null) {
                 final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
                 final Set<String> permissionSet = new HashSet<>();
-                permissionSet.add(systemHelper.getSearchRoleByUser(name));
+                permissionSet.add(systemHelper.getSearchRoleByUser(nameId));
                 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()]);
@@ -135,5 +165,29 @@ public class SamlCredential implements LoginCredential, FessCredential {
             return permissions;
         }
 
+        public String getSessionIndex() {
+            return sessionIndex;
+        }
+
+        public String getNameIdFormat() {
+            return nameIdFormat;
+        }
+
+        public String getNameidNameQualifier() {
+            return nameidNameQualifier;
+        }
+
+        public String getNameidSPNameQualifier() {
+            return nameidSPNameQualifier;
+        }
+
+        @Override
+        public String toString() {
+            return "SamlUser [groups=" + Arrays.toString(groups) + ", roles=" + Arrays.toString(roles) + ", permissions="
+                    + Arrays.toString(permissions) + ", nameId=" + nameId + ", sessionIndex=" + sessionIndex + ", nameIdFormat="
+                    + nameIdFormat + ", nameidNameQualifier=" + nameidNameQualifier + ", nameidSPNameQualifier=" + nameidSPNameQualifier
+                    + "]";
+        }
+
     }
 }

+ 11 - 3
src/main/java/org/codelibs/fess/app/web/logout/LogoutAction.java

@@ -15,9 +15,12 @@
  */
 package org.codelibs.fess.app.web.logout;
 
+import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.app.web.base.FessSearchAction;
 import org.codelibs.fess.app.web.login.LoginAction;
 import org.codelibs.fess.mylasta.action.FessUserBean;
+import org.codelibs.fess.util.ComponentUtil;
+import org.dbflute.optional.OptionalThing;
 import org.lastaflute.web.Execute;
 import org.lastaflute.web.response.HtmlResponse;
 
@@ -41,11 +44,16 @@ public class LogoutAction extends FessSearchAction {
 
     @Execute
     public HtmlResponse index() {
-        getUserBean().map(FessUserBean::getUserId).orElse("-");
-        activityHelper.logout(getUserBean());
+        OptionalThing<FessUserBean> userBean = getUserBean();
+        activityHelper.logout(userBean);
+        final String redirectUrl = userBean.map(user -> ComponentUtil.getSsoManager().logout(user)).orElse(null);
         fessLoginAssist.logout();
         userInfoHelper.deleteUserCodeFromCookie(request);
-        return redirect(LoginAction.class);
+        if (StringUtil.isNotBlank(redirectUrl)) {
+            return HtmlResponse.fromRedirectPathAsIs(redirectUrl);
+        } else {
+            return redirect(LoginAction.class);
+        }
     }
 
 }

+ 16 - 10
src/main/java/org/codelibs/fess/app/web/sso/SsoAction.java

@@ -21,9 +21,8 @@ import org.codelibs.fess.app.web.RootAction;
 import org.codelibs.fess.app.web.base.FessLoginAction;
 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
 import org.codelibs.fess.app.web.login.LoginAction;
-import org.codelibs.fess.sso.SsoAuthenticator;
 import org.codelibs.fess.sso.SsoManager;
-import org.codelibs.fess.sso.saml.SamlAuthenticator;
+import org.codelibs.fess.sso.SsoResponseType;
 import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.optional.OptionalThing;
 import org.lastaflute.web.Execute;
@@ -78,15 +77,22 @@ public class SsoAction extends FessLoginAction {
     }
 
     @Execute
-    public ActionResponse metadata(final String name) {
-        String key = name + "Authenticator";
-        if (ComponentUtil.hasComponent(key)) {
-            throw responseManager.new400("Unknown request type: " + name);
+    public ActionResponse metadata() {
+        final SsoManager ssoManager = ComponentUtil.getSsoManager();
+        final ActionResponse actionResponse = ssoManager.getResponse(SsoResponseType.METADATA);
+        if (actionResponse == null) {
+            throw responseManager.new400("Unsupported request type.");
         }
-        final SsoAuthenticator authenticator = ComponentUtil.getComponent(key);
-        if (authenticator instanceof SamlAuthenticator) {
-            return ((SamlAuthenticator) authenticator).getMetadataResponse();
+        return actionResponse;
+    }
+
+    @Execute
+    public ActionResponse logout() {
+        final SsoManager ssoManager = ComponentUtil.getSsoManager();
+        final ActionResponse actionResponse = ssoManager.getResponse(SsoResponseType.LOGOUT);
+        if (actionResponse == null) {
+            throw responseManager.new400("Unsupported request type.");
         }
-        throw responseManager.new400("Unsupported request type: " + name);
+        return actionResponse;
     }
 }

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

@@ -16,7 +16,9 @@
 package org.codelibs.fess.sso;
 
 import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
+import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.lastaflute.web.login.credential.LoginCredential;
+import org.lastaflute.web.response.ActionResponse;
 
 public interface SsoAuthenticator {
 
@@ -24,4 +26,8 @@ public interface SsoAuthenticator {
 
     void resolveCredential(LoginCredentialResolver resolver);
 
+    ActionResponse getResponse(SsoResponseType responseType);
+
+    String logout(FessUserBean user);
+
 }

+ 34 - 2
src/main/java/org/codelibs/fess/sso/SsoManager.java

@@ -20,8 +20,10 @@ import java.util.List;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.util.ComponentUtil;
 import org.lastaflute.web.login.credential.LoginCredential;
+import org.lastaflute.web.response.ActionResponse;
 
 public class SsoManager {
     private static final Logger logger = LogManager.getLogger(SsoManager.class);
@@ -42,8 +44,38 @@ public class SsoManager {
 
     public LoginCredential getLoginCredential() {
         if (available()) {
-            final SsoAuthenticator authenticator = ComponentUtil.getComponent(getSsoType() + "Authenticator");
-            return authenticator.getLoginCredential();
+            final SsoAuthenticator authenticator = getAuthenticator();
+            if (authenticator != null) {
+                return authenticator.getLoginCredential();
+            }
+        }
+        return null;
+    }
+
+    public ActionResponse getResponse(final SsoResponseType responseType) {
+        if (available()) {
+            final SsoAuthenticator authenticator = getAuthenticator();
+            if (authenticator != null) {
+                return authenticator.getResponse(responseType);
+            }
+        }
+        return null;
+    }
+
+    public String logout(final FessUserBean user) {
+        if (available()) {
+            final SsoAuthenticator authenticator = getAuthenticator();
+            if (authenticator != null) {
+                return authenticator.logout(user);
+            }
+        }
+        return null;
+    }
+
+    protected SsoAuthenticator getAuthenticator() {
+        final String name = getSsoType() + "Authenticator";
+        if (ComponentUtil.hasComponent(name)) {
+            return ComponentUtil.getComponent(name);
         }
         return null;
     }

+ 20 - 0
src/main/java/org/codelibs/fess/sso/SsoResponseType.java

@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012-2020 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;
+
+public enum SsoResponseType {
+    METADATA, LOGOUT;
+}

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

@@ -54,11 +54,14 @@ import org.codelibs.fess.app.web.base.login.AzureAdCredential.AzureAdUser;
 import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
 import org.codelibs.fess.crawler.Constants;
 import org.codelibs.fess.exception.SsoLoginException;
+import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.sso.SsoAuthenticator;
+import org.codelibs.fess.sso.SsoResponseType;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.fess.util.DocumentUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.lastaflute.web.login.credential.LoginCredential;
+import org.lastaflute.web.response.ActionResponse;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.util.LaRequestUtil;
 
@@ -589,4 +592,14 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
     public void setGroupCacheExpiry(final long groupCacheExpiry) {
         this.groupCacheExpiry = groupCacheExpiry;
     }
+
+    @Override
+    public ActionResponse getResponse(final SsoResponseType responseType) {
+        return null;
+    }
+
+    @Override
+    public String logout(final FessUserBean user) {
+        return null;
+    }
 }

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

@@ -32,10 +32,13 @@ 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.crawler.Constants;
+import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.sso.SsoAuthenticator;
+import org.codelibs.fess.sso.SsoResponseType;
 import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.lastaflute.web.login.credential.LoginCredential;
+import org.lastaflute.web.response.ActionResponse;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.util.LaRequestUtil;
 
@@ -238,4 +241,14 @@ public class OpenIdConnectAuthenticator implements SsoAuthenticator {
     public void resolveCredential(final LoginCredentialResolver resolver) {
         resolver.resolve(OpenIdConnectCredential.class, credential -> OptionalEntity.of(credential.getUser()));
     }
+
+    @Override
+    public ActionResponse getResponse(final SsoResponseType responseType) {
+        return null;
+    }
+
+    @Override
+    public String logout(final FessUserBean user) {
+        return null;
+    }
 }

+ 83 - 5
src/main/java/org/codelibs/fess/sso/saml/SamlAuthenticator.java

@@ -1,3 +1,18 @@
+/*
+ * Copyright 2012-2020 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.saml;
 
 import java.io.OutputStreamWriter;
@@ -19,9 +34,12 @@ import org.codelibs.core.net.UuidUtil;
 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.SamlCredential;
+import org.codelibs.fess.app.web.base.login.SamlCredential.SamlUser;
 import org.codelibs.fess.crawler.Constants;
 import org.codelibs.fess.exception.SsoLoginException;
+import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.sso.SsoAuthenticator;
+import org.codelibs.fess.sso.SsoResponseType;
 import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.lastaflute.web.login.credential.LoginCredential;
@@ -55,10 +73,10 @@ public class SamlAuthenticator implements SsoAuthenticator {
         defaultSettings = new HashMap<>();
         defaultSettings.put("onelogin.saml2.strict", "true");
         defaultSettings.put("onelogin.saml2.debug", "false");
-        defaultSettings.put("onelogin.saml2.sp.entityid", "http://localhost:8080/sso/metadata/saml");
+        defaultSettings.put("onelogin.saml2.sp.entityid", "http://localhost:8080/sso/metadata");
         defaultSettings.put("onelogin.saml2.sp.assertion_consumer_service.url", "http://localhost:8080/sso/");
         defaultSettings.put("onelogin.saml2.sp.assertion_consumer_service.binding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
-        defaultSettings.put("onelogin.saml2.sp.single_logout_service.url", "http://localhost:8080/sso/logout/saml");
+        defaultSettings.put("onelogin.saml2.sp.single_logout_service.url", "http://localhost:8080/sso/logout");
         defaultSettings.put("onelogin.saml2.sp.single_logout_service.binding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect");
         defaultSettings.put("onelogin.saml2.sp.nameidformat", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
         defaultSettings.put("onelogin.saml2.sp.x509cert", "");
@@ -167,12 +185,48 @@ public class SamlAuthenticator implements SsoAuthenticator {
 
     }
 
-    public ActionResponse getMetadataResponse() {
+    @Override
+    public String logout(final FessUserBean user) {
+        if (user.getFessUser() instanceof SamlUser) {
+            return LaRequestUtil
+                    .getOptionalRequest()
+                    .map(request -> {
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("Logging out with SAML Authenticator");
+                        }
+                        final HttpServletResponse response = LaResponseUtil.getResponse();
+                        final SamlUser samlUser = (SamlUser) user.getFessUser();
+                        try {
+                            final Auth auth = new Auth(getSettings(), request, response);
+                            return auth.logout(null, samlUser.getName(), samlUser.getSessionIndex(), true, samlUser.getNameIdFormat(),
+                                    samlUser.getNameidNameQualifier(), samlUser.getNameidSPNameQualifier());
+                        } catch (final Exception e) {
+                            logger.warn("Failed to logout from IdP: {}", samlUser, e);
+                        }
+                        return null;
+                    }).orElse(null);
+        }
+        return null;
+    }
+
+    @Override
+    public ActionResponse getResponse(final SsoResponseType responseType) {
+        switch (responseType) {
+        case METADATA:
+            return getMetadataResponse();
+        case LOGOUT:
+            return getLogoutResponse();
+        default:
+            return null;
+        }
+    }
+
+    protected ActionResponse getMetadataResponse() {
         return LaRequestUtil
                 .getOptionalRequest()
                 .map(request -> {
                     if (logger.isDebugEnabled()) {
-                        logger.debug("Logging in with SAML Authenticator");
+                        logger.debug("Accessing metadata with SAML Authenticator");
                     }
                     final HttpServletResponse response = LaResponseUtil.getResponse();
                     try {
@@ -198,6 +252,31 @@ public class SamlAuthenticator implements SsoAuthenticator {
                 }).orElseGet(() -> getStreamResponse("metadata", "text/html; charset=UTF-8", "Invalid state."));
     }
 
+    protected ActionResponse getLogoutResponse() {
+        return LaRequestUtil
+                .getOptionalRequest()
+                .map(request -> {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Logging out with SAML Authenticator");
+                    }
+                    final HttpServletResponse response = LaResponseUtil.getResponse();
+                    try {
+                        final Auth auth = new Auth(getSettings(), request, response);
+                        auth.processSLO();
+                        final List<String> errors = auth.getErrors();
+                        if (errors.isEmpty()) {
+                            return getStreamResponse("logout", "text/html; charset=UTF-8", "Logged out");
+                        } else {
+                            return getStreamResponse("logout", "text/html; charset=UTF-8", errors.stream().map(s -> "<p>" + s + "</p>")
+                                    .collect(Collectors.joining()));
+                        }
+                    } catch (final Exception e) {
+                        logger.warn("Failed to process logout.", e);
+                        return getStreamResponse("metadata", "text/html; charset=UTF-8", e.getMessage());
+                    }
+                }).orElseGet(() -> getStreamResponse("metadata", "text/html; charset=UTF-8", "Invalid state."));
+    }
+
     protected StreamResponse getStreamResponse(final String filename, final String contentType, final String content) {
         return new StreamResponse(filename).contentType(contentType).stream(out -> {
             try (final Writer writer = new OutputStreamWriter(out.stream(), Constants.UTF_8_CHARSET)) {
@@ -205,5 +284,4 @@ public class SamlAuthenticator implements SsoAuthenticator {
             }
         });
     }
-
 }

+ 14 - 0
src/main/java/org/codelibs/fess/sso/spnego/SpnegoAuthenticator.java

@@ -32,8 +32,10 @@ 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.exception.SsoLoginException;
+import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.sso.SsoAuthenticator;
+import org.codelibs.fess.sso.SsoResponseType;
 import org.codelibs.fess.util.ComponentUtil;
 import org.codelibs.spnego.SpnegoFilterConfig;
 import org.codelibs.spnego.SpnegoHttpFilter;
@@ -42,6 +44,7 @@ import org.codelibs.spnego.SpnegoHttpServletResponse;
 import org.codelibs.spnego.SpnegoPrincipal;
 import org.dbflute.optional.OptionalEntity;
 import org.lastaflute.web.login.credential.LoginCredential;
+import org.lastaflute.web.response.ActionResponse;
 import org.lastaflute.web.servlet.filter.RequestLoggingFilter;
 import org.lastaflute.web.util.LaRequestUtil;
 import org.lastaflute.web.util.LaResponseUtil;
@@ -244,4 +247,15 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
             return OptionalEntity.empty();
         });
     }
+
+    @Override
+    public ActionResponse getResponse(final SsoResponseType responseType) {
+        return null;
+    }
+
+    @Override
+    public String logout(final FessUserBean user) {
+        return null;
+    }
+
 }