fix #1633 refactoring
This commit is contained in:
parent
947786fcb8
commit
df3b2598e0
5 changed files with 109 additions and 62 deletions
|
@ -15,11 +15,13 @@
|
|||
*/
|
||||
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.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codelibs.core.lang.StringUtil;
|
||||
import org.codelibs.fess.entity.FessUser;
|
||||
import org.codelibs.fess.helper.SystemHelper;
|
||||
import org.codelibs.fess.util.ComponentUtil;
|
||||
|
@ -46,7 +48,25 @@ public class AzureAdCredential implements LoginCredential, FessCredential {
|
|||
}
|
||||
|
||||
public User getUser() {
|
||||
return new User(authResult.getUserInfo().getDisplayableId(), new String[0], new String[0]);
|
||||
return new User(authResult.getUserInfo().getDisplayableId(), getDefaultGroupsAsArray(), getDefaultRolesAsArray());
|
||||
}
|
||||
|
||||
protected static String[] getDefaultGroupsAsArray() {
|
||||
final String value = ComponentUtil.getFessConfig().getSystemProperty("azuread.default.groups");
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return StringUtil.EMPTY_STRINGS;
|
||||
} else {
|
||||
return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
|
||||
}
|
||||
}
|
||||
|
||||
protected static String[] getDefaultRolesAsArray() {
|
||||
final String value = ComponentUtil.getFessConfig().getSystemProperty("azuread.default.roles");
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return StringUtil.EMPTY_STRINGS;
|
||||
} else {
|
||||
return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
|
||||
}
|
||||
}
|
||||
|
||||
public static class User implements FessUser {
|
||||
|
|
|
@ -15,15 +15,16 @@
|
|||
*/
|
||||
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.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.codelibs.core.lang.StringUtil;
|
||||
import org.codelibs.fess.entity.FessUser;
|
||||
import org.codelibs.fess.helper.SystemHelper;
|
||||
import org.codelibs.fess.mylasta.direction.FessConfig;
|
||||
import org.codelibs.fess.util.ComponentUtil;
|
||||
import org.lastaflute.web.login.credential.LoginCredential;
|
||||
|
||||
|
@ -46,8 +47,25 @@ public class OpenIdConnectCredential implements LoginCredential, FessCredential
|
|||
}
|
||||
|
||||
public User getUser() {
|
||||
final FessConfig fessConfig = ComponentUtil.getFessConfig();
|
||||
return new User(getUserId(), fessConfig.getOicDefaultGroupsAsArray(), fessConfig.getOicDefaultRolesAsArray());
|
||||
return new User(getUserId(), getDefaultGroupsAsArray(), getDefaultRolesAsArray());
|
||||
}
|
||||
|
||||
protected static String[] getDefaultGroupsAsArray() {
|
||||
final String value = ComponentUtil.getFessConfig().getSystemProperty("oic.default.groups");
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return StringUtil.EMPTY_STRINGS;
|
||||
} else {
|
||||
return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
|
||||
}
|
||||
}
|
||||
|
||||
protected static String[] getDefaultRolesAsArray() {
|
||||
final String value = ComponentUtil.getFessConfig().getSystemProperty("oic.default.roles");
|
||||
if (StringUtil.isBlank(value)) {
|
||||
return StringUtil.EMPTY_STRINGS;
|
||||
} else {
|
||||
return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
|
||||
}
|
||||
}
|
||||
|
||||
public static class User implements FessUser {
|
||||
|
|
|
@ -120,10 +120,6 @@ public interface FessProp {
|
|||
|
||||
String INDEX_ADMIN_DOUBLE_FIELD_SET = "indexAdminDoubleFieldSet";
|
||||
|
||||
String OIC_DEFAULT_ROLES = "oicDefaultRoles";
|
||||
|
||||
String OIC_DEFAULT_GROUPS = "oicDefaultGroups";
|
||||
|
||||
String AUTHENTICATION_ADMIN_ROLES = "authenticationAdminRoles";
|
||||
|
||||
String SEARCH_GUEST_PERMISSION_LIST = "searchGuestPermissionList";
|
||||
|
@ -1203,38 +1199,6 @@ public interface FessProp {
|
|||
return list;
|
||||
}
|
||||
|
||||
default String[] getOicDefaultGroupsAsArray() {
|
||||
String[] array = (String[]) propMap.get(OIC_DEFAULT_GROUPS);
|
||||
if (array == null) {
|
||||
final String oicDefaultGroups = getSystemProperty("oic.default.groups");
|
||||
if (StringUtil.isBlank(oicDefaultGroups)) {
|
||||
array = StringUtil.EMPTY_STRINGS;
|
||||
} else {
|
||||
array =
|
||||
split(oicDefaultGroups, ",").get(
|
||||
stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
|
||||
}
|
||||
propMap.put(OIC_DEFAULT_GROUPS, array);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
default String[] getOicDefaultRolesAsArray() {
|
||||
String[] array = (String[]) propMap.get(OIC_DEFAULT_ROLES);
|
||||
if (array == null) {
|
||||
final String oicDefaultRoles = getSystemProperty("oic.default.roles");
|
||||
if (StringUtil.isBlank(oicDefaultRoles)) {
|
||||
array = StringUtil.EMPTY_STRINGS;
|
||||
} else {
|
||||
array =
|
||||
split(oicDefaultRoles, ",").get(
|
||||
stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).toArray(n -> new String[n]));
|
||||
}
|
||||
propMap.put(OIC_DEFAULT_ROLES, array);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
String getIndexAdminArrayFields();
|
||||
|
||||
default Set<String> getIndexAdminArrayFieldSet() {
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.codelibs.core.net.UuidUtil;
|
|||
import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
|
||||
import org.codelibs.fess.app.web.base.login.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;
|
||||
|
@ -73,7 +72,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
|
||||
protected static final String STATES = "aadStates";
|
||||
|
||||
protected static final String STATE = "aadState";
|
||||
protected static final String STATE = "state";
|
||||
|
||||
protected static final String ERROR = "error";
|
||||
|
||||
|
@ -115,10 +114,15 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
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;
|
||||
final String authUrl =
|
||||
getAuthority() + getTenant()
|
||||
+ "/oauth2/authorize?response_type=code&scope=directory.read.all&response_mode=form_post&redirect_uri="
|
||||
+ URLEncoder.encode(request.getRequestURL().toString(), Constants.UTF_8_CHARSET) + "&client_id=" + getClientId()
|
||||
+ "&resource=https%3a%2f%2fgraph.microsoft.com" + "&state=" + state + "&nonce=" + nonce;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("redirect to: {}", authUrl);
|
||||
}
|
||||
return authUrl;
|
||||
|
||||
}
|
||||
|
||||
|
@ -129,7 +133,11 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
stateMap = new HashMap<>();
|
||||
session.setAttribute(STATES, stateMap);
|
||||
}
|
||||
stateMap.put(state, new StateData(nonce, System.currentTimeMillis()));
|
||||
final StateData stateData = new StateData(nonce, System.currentTimeMillis());
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("store {} in session", stateData);
|
||||
}
|
||||
stateMap.put(state, stateData);
|
||||
}
|
||||
|
||||
protected LoginCredential processAuthenticationData(final HttpServletRequest request) {
|
||||
|
@ -145,19 +153,21 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
params.put(e.getKey(), e.getValue()[0]);
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("process authentication: url: {}, params: {}", urlBuf, params);
|
||||
}
|
||||
|
||||
// validate that state in response equals to state in request
|
||||
final StateData stateData = validateState(request.getSession(), params.get(STATE));
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("load {}", stateData);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -169,6 +179,9 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
}
|
||||
|
||||
protected AuthenticationResponse parseAuthenticationResponse(final String url, final Map<String, String> params) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Parse: {} : {}", url, params);
|
||||
}
|
||||
try {
|
||||
return AuthenticationResponseParser.parse(new URI(url), params);
|
||||
} catch (final Exception e) {
|
||||
|
@ -178,6 +191,9 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
|
||||
protected void validateNonce(final StateData stateData, final AuthenticationResult authData) {
|
||||
final String idToken = authData.getIdToken();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("idToken: {}", idToken);
|
||||
}
|
||||
try {
|
||||
final JWTClaimsSet claimsSet = JWTParser.parse(idToken).getJWTClaimsSet();
|
||||
if (claimsSet == null) {
|
||||
|
@ -185,6 +201,9 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
}
|
||||
|
||||
final String nonce = (String) claimsSet.getClaim("nonce");
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("nonce: {}", nonce);
|
||||
}
|
||||
if (StringUtils.isEmpty(nonce) || !nonce.equals(stateData.getNonce())) {
|
||||
throw new SsoLoginException("could not validate nonce");
|
||||
}
|
||||
|
@ -196,12 +215,16 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
}
|
||||
|
||||
protected AuthenticationResult getAccessToken(final AuthorizationCode authorizationCode, final String currentUri) {
|
||||
final String authority = getAuthority() + getTenant() + "/";
|
||||
final String authCode = authorizationCode.getValue();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("authCode: {}, authority: {}, uri: {}", authCode, authority, currentUri);
|
||||
}
|
||||
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 AuthenticationContext context = new AuthenticationContext(authority, true, service);
|
||||
final Future<AuthenticationResult> future =
|
||||
context.acquireTokenByAuthorizationCode(authCode, new URI(currentUri), credential, null);
|
||||
final AuthenticationResult result = future.get();
|
||||
|
@ -240,9 +263,17 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
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));
|
||||
.collect(Collectors.toList()).forEach(s -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("remove old state: {}", s);
|
||||
}
|
||||
states.remove(s);
|
||||
});
|
||||
final StateData stateData = states.get(state);
|
||||
if (stateData != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("restore {} from session", stateData);
|
||||
}
|
||||
states.remove(state);
|
||||
return stateData;
|
||||
}
|
||||
|
@ -274,6 +305,11 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
public long getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateData [nonce=" + nonce + ", expiration=" + expiration + "]";
|
||||
}
|
||||
}
|
||||
|
||||
protected String getClientId() {
|
||||
|
@ -289,7 +325,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
}
|
||||
|
||||
protected String getAuthority() {
|
||||
return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_AUTHORITY, StringUtil.EMPTY);
|
||||
return ComponentUtil.getFessConfig().getSystemProperty(AZUREAD_AUTHORITY, "https://login.microsoftonline.com/");
|
||||
}
|
||||
|
||||
protected long getStateTtl() {
|
||||
|
@ -298,7 +334,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
|
|||
|
||||
@Override
|
||||
public void resolveCredential(final LoginCredentialResolver resolver) {
|
||||
resolver.resolve(OpenIdConnectCredential.class, credential -> {
|
||||
resolver.resolve(AzureAdCredential.class, credential -> {
|
||||
return OptionalEntity.of(credential.getUser());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
|
|||
import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
|
||||
import org.codelibs.fess.app.web.base.login.SpnegoCredential;
|
||||
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;
|
||||
|
@ -45,8 +46,10 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SpnegoAuthenticator implements SsoAuthenticator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SpnegoAuthenticator.class);
|
||||
|
||||
protected static final String SPNEGO_INITIALIZED = "spnego.initialized";
|
||||
protected static final String SPNEGO_EXCLUDE_DIRS = "spnego.exclude.dirs";
|
||||
protected static final String SPNEGO_ALLOW_DELEGATION = "spnego.allow.delegation";
|
||||
protected static final String SPNEGO_ALLOW_LOCALHOST = "spnego.allow.localhost";
|
||||
|
@ -65,6 +68,14 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
|
|||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
ComponentUtil.getSsoManager().register(this);
|
||||
}
|
||||
|
||||
protected synchronized org.codelibs.spnego.SpnegoAuthenticator getAuthenticator() {
|
||||
final FessConfig fessConfig = ComponentUtil.getFessConfig();
|
||||
if (authenticator != null && fessConfig.getSystemPropertyAsBoolean(SPNEGO_INITIALIZED, false)) {
|
||||
return authenticator;
|
||||
}
|
||||
try {
|
||||
// set some System properties
|
||||
final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
|
||||
|
@ -72,13 +83,11 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
|
|||
// pre-authenticate
|
||||
authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
|
||||
|
||||
ComponentUtil.getSsoManager().register(this);
|
||||
fessConfig.setSystemPropertyAsBoolean(SPNEGO_INITIALIZED, true);
|
||||
fessConfig.storeSystemProperties();
|
||||
return authenticator;
|
||||
} catch (final Exception e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.warn("Failed to initialize SPNEGO.", e);
|
||||
} else {
|
||||
logger.warn("Failed to initialize SPNEGO.");
|
||||
}
|
||||
throw new SsoLoginException("Failed to initialize SPNEGO.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +105,7 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
|
|||
// client/caller principal
|
||||
final SpnegoPrincipal principal;
|
||||
try {
|
||||
principal = authenticator.authenticate(request, spnegoResponse);
|
||||
principal = getAuthenticator().authenticate(request, spnegoResponse);
|
||||
} catch (final Exception e) {
|
||||
final String msg = "HTTP Authorization Header=" + request.getHeader(Constants.AUTHZ_HEADER);
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
|
Loading…
Add table
Reference in a new issue