diff --git a/src/main/java/org/codelibs/fess/app/web/base/login/AzureAdCredential.java b/src/main/java/org/codelibs/fess/app/web/base/login/AzureAdCredential.java
index 5c37b8ce8e081ed74d26d9e06c3bfc894dcfeabf..b721fc196786d01060d0e78be2878912c0cc54e9 100644
--- a/src/main/java/org/codelibs/fess/app/web/base/login/AzureAdCredential.java
+++ b/src/main/java/org/codelibs/fess/app/web/base/login/AzureAdCredential.java
@@ -15,13 +15,11 @@
*/
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.sso.aad.AzureAdAuthenticator;
@@ -49,25 +47,7 @@ public class AzureAdCredential implements LoginCredential, FessCredential {
}
public AzureAdUser getUser() {
- return new AzureAdUser(authResult, 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]));
- }
+ return new AzureAdUser(authResult);
}
public static class AzureAdUser implements FessUser {
@@ -81,10 +61,10 @@ public class AzureAdCredential implements LoginCredential, FessCredential {
protected AuthenticationResult authResult;
- public AzureAdUser(final AuthenticationResult authResult, final String[] groups, final String[] roles) {
+ public AzureAdUser(final AuthenticationResult authResult) {
this.authResult = authResult;
- this.groups = groups;
- this.roles = roles;
+ final AzureAdAuthenticator authenticator = ComponentUtil.getComponent(AzureAdAuthenticator.class);
+ authenticator.updateMemberOf(this);
}
@Override
@@ -123,7 +103,21 @@ public class AzureAdCredential implements LoginCredential, FessCredential {
final AzureAdAuthenticator authenticator = ComponentUtil.getComponent(AzureAdAuthenticator.class);
final String refreshToken = authResult.getRefreshToken();
authResult = authenticator.getAccessToken(refreshToken);
- return false;
+ authenticator.updateMemberOf(this);
+ permissions = null;
+ return true;
+ }
+
+ public AuthenticationResult getAuthenticationResult() {
+ return authResult;
+ }
+
+ public void setGroups(final String[] groups) {
+ this.groups = groups;
+ }
+
+ public void setRoles(final String[] roles) {
+ this.roles = roles;
}
}
}
diff --git a/src/main/java/org/codelibs/fess/sso/aad/AzureAdAuthenticator.java b/src/main/java/org/codelibs/fess/sso/aad/AzureAdAuthenticator.java
index b96ee99ecd674e030f8ab445d1e1e192be4c53f4..b95eb7b181d2482018cd91938a2bd1b95e70874f 100644
--- a/src/main/java/org/codelibs/fess/sso/aad/AzureAdAuthenticator.java
+++ b/src/main/java/org/codelibs/fess/sso/aad/AzureAdAuthenticator.java
@@ -15,9 +15,16 @@
*/
package org.codelibs.fess.sso.aad;
+import static org.codelibs.core.stream.StreamUtil.split;
+
+import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -32,8 +39,12 @@ 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.curl.Curl;
+import org.codelibs.curl.CurlResponse;
+import org.codelibs.elasticsearch.runner.net.EcrCurl;
import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
import org.codelibs.fess.app.web.base.login.AzureAdCredential;
+import org.codelibs.fess.app.web.base.login.AzureAdCredential.AzureAdUser;
import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
import org.codelibs.fess.crawler.Constants;
import org.codelibs.fess.exception.SsoLoginException;
@@ -61,15 +72,15 @@ 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_STATE_TTL = "aad.state.ttl";
- protected static final String AZUREAD_AUTHORITY = "azuread.authority";
+ protected static final String AZUREAD_AUTHORITY = "aad.authority";
- protected static final String AZUREAD_TENANT = "azuread.tenant";
+ protected static final String AZUREAD_TENANT = "aad.tenant";
- protected static final String AZUREAD_CLIENT_SECRET = "azuread.client.secret";
+ protected static final String AZUREAD_CLIENT_SECRET = "aad.client.secret";
- protected static final String AZUREAD_CLIENT_ID = "azuread.client.id";
+ protected static final String AZUREAD_CLIENT_ID = "aad.client.id";
protected static final String STATES = "aadStates";
@@ -215,7 +226,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
}
}
- public AuthenticationResult getAccessToken(String refreshToken) {
+ public AuthenticationResult getAccessToken(final String refreshToken) {
final String authority = getAuthority() + getTenant() + "/";
if (logger.isDebugEnabled()) {
logger.debug("refreshToken: {}, authority: {}", refreshToken, authority);
@@ -223,15 +234,15 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
- AuthenticationContext context = new AuthenticationContext(authority, true, service);
- Future<AuthenticationResult> future =
+ final AuthenticationContext context = new AuthenticationContext(authority, true, service);
+ final Future<AuthenticationResult> future =
context.acquireTokenByRefreshToken(refreshToken, new ClientCredential(getClientId(), getClientSecret()), null, null);
final AuthenticationResult result = future.get(acquisitionTimeout, TimeUnit.MILLISECONDS);
if (result == null) {
throw new SsoLoginException("authentication result was null");
}
return result;
- } catch (Exception e) {
+ } catch (final Exception e) {
throw new SsoLoginException("Failed to get a token.", e);
} finally {
if (service != null) {
@@ -315,6 +326,74 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
return params.containsKey(ERROR) || params.containsKey(ID_TOKEN) || params.containsKey(CODE);
}
+ public void updateMemberOf(final AzureAdUser user) {
+ final List<String> groupList = new ArrayList<>();
+ final List<String> roleList = new ArrayList<>();
+ groupList.addAll(getDefaultGroupList());
+ roleList.addAll(getDefaultRoleList());
+ try (CurlResponse response =
+ Curl.get("https://graph.microsoft.com/v1.0/me/memberOf")
+ .header("Authorization", "Bearer " + user.getAuthenticationResult().getAccessToken())
+ .header("Accept", "application/json").execute()) {
+ final Map<String, Object> contentMap = response.getContent(EcrCurl.jsonParser);
+ if (contentMap.containsKey("value")) {
+ @SuppressWarnings("unchecked")
+ final List<Map<String, Object>> memberOfList = (List<Map<String, Object>>) contentMap.get("value");
+ for (final Map<String, Object> memberOf : memberOfList) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("member: {}", memberOf);
+ }
+ final String id = (String) memberOf.get("id");
+ if (StringUtil.isBlank(id)) {
+ logger.warn("id is empty: {}", memberOf);
+ continue;
+ }
+ String memberType = (String) memberOf.get("@odata.type");
+ if (memberType == null) {
+ logger.warn("@odata.type is null: {}", memberOf);
+ continue;
+ }
+ memberType = memberType.toLowerCase(Locale.ENGLISH);
+ if (memberType.contains("group")) {
+ groupList.add(id);
+ } else if (memberType.contains("role")) {
+ roleList.add(id);
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("unknown @odata.type: {}", memberOf);
+ }
+ groupList.add(id);
+ }
+ }
+ } else if (contentMap.containsKey("error")) {
+ logger.warn("Failed to access groups/roles: {}", contentMap);
+ }
+ } catch (final IOException e) {
+ logger.warn("Failed to access groups/roles in AzureAD.", e);
+ }
+
+ user.setGroups(groupList.toArray(n -> new String[n]));
+ user.setRoles(roleList.toArray(n -> new String[n]));
+ }
+
+ protected List<String> getDefaultGroupList() {
+ final String value = ComponentUtil.getFessConfig().getSystemProperty("aad.default.groups");
+ if (StringUtil.isBlank(value)) {
+ return Collections.emptyList();
+ } else {
+ return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).collect(Collectors.toList()));
+ }
+ }
+
+ protected List<String> getDefaultRoleList() {
+ final String value = ComponentUtil.getFessConfig().getSystemProperty("aad.default.roles");
+ if (StringUtil.isBlank(value)) {
+ return Collections.emptyList();
+ } else {
+ return split(value, ",").get(stream -> stream.filter(StringUtil::isNotBlank).map(s -> s.trim()).collect(Collectors.toList()));
+ }
+ }
+
protected class StateData {
private final String nonce;
private final long expiration;
@@ -365,7 +444,7 @@ public class AzureAdAuthenticator implements SsoAuthenticator {
});
}
- public void setAcquisitionTimeout(long acquisitionTimeout) {
+ public void setAcquisitionTimeout(final long acquisitionTimeout) {
this.acquisitionTimeout = acquisitionTimeout;
}
}