Skip to content
This repository has been archived by the owner on Dec 31, 2021. It is now read-only.

Commit

Permalink
Support the Keycloak Service Account login and authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
flytreeleft committed Aug 8, 2021
1 parent 3c2f651 commit 3e8b564
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

Expand All @@ -29,6 +30,7 @@
import org.github.flytreeleft.nexus3.keycloak.plugin.internal.http.ClientAuthenticator;
import org.github.flytreeleft.nexus3.keycloak.plugin.internal.http.Http;
import org.github.flytreeleft.nexus3.keycloak.plugin.internal.http.HttpMethod;
import org.github.flytreeleft.nexus3.keycloak.plugin.internal.mapper.KeycloakMapper;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.constants.ServiceUrlConstants;
Expand Down Expand Up @@ -82,16 +84,25 @@ public AccessTokenResponse obtainAccessToken(String username, String password) {
.build(getRealm());
HttpMethod<AccessTokenResponse> httpMethod = getHttp().post(uri);

httpMethod = httpMethod.form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)
.param("username", username)
.param("password", password);

if (getConfig().isPublicClient()) {
httpMethod.param(OAuth2Constants.CLIENT_ID, getConfig().getResource());
if (KeycloakMapper.isServiceAccount(username)) {
String clientId = KeycloakMapper.getClientIdFromServiceAccount(username);
// https://github.com/keycloak/keycloak-documentation/blob/master/server_admin/topics/clients/oidc/service-accounts.adoc
httpMethod = httpMethod.form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
.authorizationBasic(clientId, password);
logger.info("Try to obtain the access token for the service account {}", username);
} else {
httpMethod.authorizationBasic(getConfig().getResource(),
getConfig().getCredentials().get("secret").toString());
httpMethod = httpMethod.form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)
.param("username", username)
.param("password", password);

if (getConfig().isPublicClient()) {
httpMethod = httpMethod.param(OAuth2Constants.CLIENT_ID, getConfig().getResource());
} else {
httpMethod = httpMethod.authorizationBasic(getConfig().getResource(),
getConfig().getCredentials().get("secret").toString());
}
}

return httpMethod.response().json(AccessTokenResponse.class).execute();
Expand Down Expand Up @@ -122,18 +133,36 @@ public UserRepresentation getUser(String userNameOrEmail) {
return null;
}

HttpMethod<List<UserRepresentation>> httpMethod = getHttp().get("/admin/realms/%s/users", getRealm());

boolean isEmail = isEmail(userNameOrEmail);
if (isEmail) {
httpMethod.param("email", userNameOrEmail);
List<UserRepresentation> users = null;

if (KeycloakMapper.isServiceAccount(userNameOrEmail)) {
String clientId = KeycloakMapper.getClientIdFromServiceAccount(userNameOrEmail);
ClientRepresentation client = getRealmClient(clientId);

HttpMethod<UserRepresentation> httpMethod
= getHttp().get("/admin/realms/%s/clients/%s/service-account-user", getRealm(), client.getId());

logger.info("Try to get UserRepresentation for the service account {}", userNameOrEmail);

UserRepresentation user = httpMethod.authentication().response().json(UserRepresentation.class).execute();
if (user != null) {
users = Collections.singletonList(user);
}
} else {
httpMethod.param("username", userNameOrEmail);
HttpMethod<List<UserRepresentation>> httpMethod = getHttp().get("/admin/realms/%s/users", getRealm());

if (isEmail) {
httpMethod = httpMethod.param("email", userNameOrEmail);
} else {
httpMethod = httpMethod.param("username", userNameOrEmail);
}

users = httpMethod.authentication()
.response()
.json(new TypeReference<List<UserRepresentation>>() {})
.execute();
}
List<UserRepresentation> users = httpMethod.authentication()
.response()
.json(new TypeReference<List<UserRepresentation>>() {})
.execute();

if (users != null) {
for (UserRepresentation user : users) {
Expand Down Expand Up @@ -265,7 +294,7 @@ public List<RoleRepresentation> getRealmRolesOfUser(UserRepresentation user) {
}

public List<GroupRepresentation> getRealmGroupsOfUser(UserRepresentation user) {
if (user == null) {
if (user == null || KeycloakMapper.isServiceAccount(user.getUsername())) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,39 @@ public class KeycloakMapper {
public static final String REALM_ROLE_PREFIX = "RealmRole";
public static final String REALM_GROUP_PREFIX = "RealmGroup";

public static final String USER_SERVICE_ACCOUNT = "ServiceAccount";
public static final String USER_SERVICE_ACCOUNT_PREFIX = "service-account-";

public static boolean isServiceAccount(String username) {
return username.startsWith(USER_SERVICE_ACCOUNT_PREFIX);
}

public static String getClientIdFromServiceAccount(String serviceAccount) {
return serviceAccount.replaceAll("^" + USER_SERVICE_ACCOUNT_PREFIX + "([^@]+).*$", "$1");
}

public static User toUser(String source, UserRepresentation representation) {
if (representation == null) {
return null;
}

String username = representation.getUsername();
User user = new User();
user.setUserId(representation.getUsername());
user.setFirstName(representation.getFirstName());
user.setLastName(representation.getLastName());

user.setUserId(username);
user.setEmailAddress(representation.getEmail());
user.setReadOnly(true);
user.setStatus(representation.isEnabled() ? UserStatus.active : UserStatus.disabled);
user.setSource(source);

if (isServiceAccount(username)) {
user.setFirstName(getClientIdFromServiceAccount(username));
user.setLastName(USER_SERVICE_ACCOUNT);
} else {
user.setFirstName(representation.getFirstName());
user.setLastName(representation.getLastName());
}

return user;
}

Expand Down Expand Up @@ -88,8 +108,8 @@ public static Role toRole(String source, String sourceCode, GroupRepresentation
/** Just for compatibility */
public static Set<String> toCompatibleRoleIds(String source, List<?>... lists) {
return toRoles(source, null, lists, true).stream()
.map(Role::getRoleId)
.collect(Collectors.toCollection(LinkedHashSet::new));
.map(Role::getRoleId)
.collect(Collectors.toCollection(LinkedHashSet::new));
}

public static Set<String> toRoleIds(String source, String sourceCode, List<?>... lists) {
Expand Down Expand Up @@ -133,6 +153,7 @@ private static Role toCompatibleRole(String source, RoleRepresentation represent
role.setDescription(representation.getDescription());
role.setReadOnly(true);
role.setSource(source);

return role;
}
}

0 comments on commit 3e8b564

Please sign in to comment.