This commit is contained in:
Shinsuke Sugaya 2020-07-05 23:17:47 +09:00
parent 59a80ec2d5
commit d4a16f872e
10 changed files with 271 additions and 27 deletions

View file

@ -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
+ "]";
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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 {
}
});
}
}

View file

@ -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;
}
}