#1633 azure ad support

This commit is contained in:
Shinsuke Sugaya 2019-03-16 22:50:30 +09:00
parent 7d0c262849
commit 947786fcb8
20 changed files with 706 additions and 552 deletions

15
pom.xml
View file

@ -1332,6 +1332,21 @@
</exclusion>
</exclusions>
</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 -->
<dependency>

View file

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

View file

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

View file

@ -15,6 +15,8 @@
*/
package org.codelibs.fess.app.web.base.login;
import java.util.function.Function;
import javax.annotation.Resource;
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.mylasta.action.FessUserBean;
import org.codelibs.fess.mylasta.direction.FessConfig;
import org.codelibs.fess.sso.SsoAuthenticator;
import org.codelibs.fess.util.ComponentUtil;
import org.dbflute.optional.OptionalEntity;
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.TypicalLoginAssist;
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.option.LoginSpecifiedOption;
import org.lastaflute.web.servlet.session.SessionManager;
@ -140,8 +142,8 @@ public class FessLoginAssist extends TypicalLoginAssist<String, FessUserBean, Fe
@Override
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 password = userCredential.getPassword();
if (!fessConfig.isAdminUser(username)) {
@ -152,16 +154,23 @@ public class FessLoginAssist extends TypicalLoginAssist<String, FessUserBean, Fe
}
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) {

View file

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

View file

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

View file

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

View file

@ -16,10 +16,10 @@
package org.codelibs.fess.app.web.login;
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.dbflute.optional.OptionalThing;
import org.lastaflute.web.Execute;
import org.lastaflute.web.login.credential.UserPasswordCredential;
import org.lastaflute.web.login.exception.LoginFailureException;
import org.lastaflute.web.response.HtmlResponse;
@ -52,13 +52,13 @@ public class LoginAction extends FessLoginAction {
final String password = form.password;
form.clearSecurityInfo();
try {
return fessLoginAssist.loginRedirect(new UserPasswordCredential(username, password), op -> {}, () -> {
return fessLoginAssist.loginRedirect(new LocalUserCredential(username, password), op -> {}, () -> {
activityHelper.login(getUserBean());
userInfoHelper.deleteUserCodeFromCookie(request);
return getHtmlResponse();
});
} 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));
}
return redirect(getClass());

View file

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

View file

@ -24,13 +24,11 @@ import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
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.util.ComponentUtil;
import org.dbflute.optional.OptionalThing;
import org.lastaflute.web.login.credential.LoginCredential;
import org.lastaflute.web.login.credential.UserPasswordCredential;
import org.lastaflute.web.util.LaRequestUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -74,18 +72,10 @@ public class ActivityHelper {
buffer.append('\t');
buffer.append("class:");
buffer.append(c.getClass().getSimpleName());
if (c instanceof OpenIdConnectCredential) {
if (c instanceof FessCredential) {
buffer.append('\t');
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();
}).ifPresent(buf::append);

View file

@ -1397,72 +1397,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
/** The key of the configuration. e.g. 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}.
* @param propertyKey The key of the property. (NotNull)
@ -5908,220 +5842,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
*/
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.
* @author FreeGen
@ -8467,126 +8187,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
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
protected java.util.Map<String, String> 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_GID_NUMBER, "gidNumber");
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.DEVELOPMENT_HERE, "true");
defaultMap.put(FessConfig.ENVIRONMENT_TITLE, "Local Development");

View file

@ -1203,16 +1203,15 @@ public interface FessProp {
return list;
}
String getOicDefaultGroups();
default String[] getOicDefaultGroupsAsArray() {
String[] array = (String[]) propMap.get(OIC_DEFAULT_GROUPS);
if (array == null) {
if (StringUtil.isBlank(getOicDefaultGroups())) {
final String oicDefaultGroups = getSystemProperty("oic.default.groups");
if (StringUtil.isBlank(oicDefaultGroups)) {
array = StringUtil.EMPTY_STRINGS;
} else {
array =
split(getOicDefaultGroups(), ",").get(
split(oicDefaultGroups, ",").get(
stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
}
propMap.put(OIC_DEFAULT_GROUPS, array);
@ -1220,16 +1219,15 @@ public interface FessProp {
return array;
}
String getOicDefaultRoles();
default String[] getOicDefaultRolesAsArray() {
String[] array = (String[]) propMap.get(OIC_DEFAULT_ROLES);
if (array == null) {
if (StringUtil.isBlank(getOicDefaultRoles())) {
final String oicDefaultRoles = getSystemProperty("oic.default.roles");
if (StringUtil.isBlank(oicDefaultRoles)) {
array = StringUtil.EMPTY_STRINGS;
} else {
array =
split(getOicDefaultRoles(), ",").get(
split(oicDefaultRoles, ",").get(
stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
}
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);
if (list == null) {
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) {
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);
if (list == null) {
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) {
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);
if (list == null) {
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) {
return new Pair<>(values[0], values[1]);
}

View file

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

View file

@ -15,31 +15,47 @@
*/
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.lastaflute.web.login.credential.LoginCredential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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() {
return authenticator != null;
return !NONE.equals(getSsoType());
}
public LoginCredential getLoginCredential() {
if (authenticator != null) {
if (available()) {
final SsoAuthenticator authenticator = ComponentUtil.getComponent(getSsoType() + "Authenticator");
return authenticator.getLoginCredential();
}
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);
}
}

View file

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

View file

@ -20,17 +20,19 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
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.FessLoginAssist.LoginCredentialResolver;
import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
import org.codelibs.fess.crawler.Constants;
import org.codelibs.fess.mylasta.direction.FessConfig;
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;
@ -53,11 +55,28 @@ public class OpenIdConnectAuthenticator implements SsoAuthenticator {
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
public LoginCredential getLoginCredential() {
@ -84,13 +103,12 @@ public class OpenIdConnectAuthenticator implements SsoAuthenticator {
}
protected String getAuthUrl(final HttpServletRequest request) {
final FessConfig fessConfig = ComponentUtil.getFessConfig();
final String state = UuidUtil.create();
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"))//
.setRedirectUri(fessConfig.getOicRedirectUrl())//
.setRedirectUri(getOicRedirectUrl())//
.setState(state)//
.build();
}
@ -174,12 +192,42 @@ public class OpenIdConnectAuthenticator implements SsoAuthenticator {
}
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")//
.setRedirectUri(fessConfig.getOicRedirectUrl())//
.set("client_id", fessConfig.getOicClientId())//
.set("client_secret", fessConfig.getOicClientSecret())//
.setRedirectUri(getOicRedirectUrl())//
.set("client_id", getOicClientId())//
.set("client_secret", getOicClientSecret())//
.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());
});
}
}

View file

@ -26,10 +26,9 @@ import javax.servlet.http.HttpServletResponse;
import org.codelibs.core.io.ResourceUtil;
import org.codelibs.core.lang.StringUtil;
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.FessSystemException;
import org.codelibs.fess.exception.SsoLoginException;
import org.codelibs.fess.mylasta.direction.FessConfig;
import org.codelibs.fess.sso.SsoAuthenticator;
import org.codelibs.fess.util.ComponentUtil;
import org.codelibs.spnego.SpnegoFilterConfig;
@ -37,6 +36,7 @@ import org.codelibs.spnego.SpnegoHttpFilter;
import org.codelibs.spnego.SpnegoHttpFilter.Constants;
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.servlet.filter.RequestLoggingFilter;
import org.lastaflute.web.util.LaRequestUtil;
@ -47,19 +47,37 @@ import org.slf4j.LoggerFactory;
public class SpnegoAuthenticator implements SsoAuthenticator {
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;
@PostConstruct
public void init() {
if ("spnego".equals(ComponentUtil.getFessConfig().getSsoType())) {
try {
// set some System properties
final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
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);
// 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 FessConfig fessConfig = ComponentUtil.getFessConfig();
@Override
public String getFilterName() {
return SpnegoAuthenticator.class.getName();
@ -131,8 +147,9 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
@Override
public String getInitParameter(final String 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()) {
return "3";
} else if (logger.isInfoEnabled()) {
@ -145,33 +162,37 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
return "0";
}
} 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)) {
return getResourcePath(fessConfig.getSpnegoKrb5Conf());
return getResourcePath(getProperty(SPNEGO_KRB5_CONF, "krb5.conf"));
} 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)) {
return fessConfig.getSpnegoLoginServerModule();
return getProperty(SPNEGO_LOGIN_SERVER_MODULE, "spnego-server");
} 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)) {
return fessConfig.getSpnegoPreauthPassword();
return getProperty(SPNEGO_PREAUTH_PASSWORD, "password");
} 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)) {
return fessConfig.getSpnegoAllowUnsecureBasic();
return getProperty(SPNEGO_ALLOW_UNSECURE_BASIC, "true");
} 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)) {
return fessConfig.getSpnegoAllowLocalhost();
return getProperty(SPNEGO_ALLOW_LOCALHOST, "true");
} 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)) {
return fessConfig.getSpnegoExcludeDirs();
return getProperty(SPNEGO_EXCLUDE_DIRS, StringUtil.EMPTY);
}
return null;
}
protected String getProperty(final String key, final String defaultValue) {
return ComponentUtil.getSystemProperties().getProperty(key, defaultValue);
}
protected String getResourcePath(final String path) {
final File file = ResourceUtil.getResourceAsFileNoException(path);
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();
});
}
}

View file

@ -686,26 +686,26 @@ ldap.attr.homeDirectory=homeDirectory
# ----------------------------------------------------------
# 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=
#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=
#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=

View file

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

View file

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