Skip to content

Commit

Permalink
fix: correct file structure (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxime1907 authored Dec 12, 2022
1 parent fd9ca9d commit 295726a
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 145 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ Request an action token for a set of specific actions.
- https://www.janua.fr/action-token-in-keycloak/
- https://stackoverflow.com/questions/67006007/how-to-generate-and-use-login-action-token-for-keycloak-user-update-profile-in-e
- https://github.com/keycloak/keycloak/blob/264c5a6cdb2fb86e20536ea4302d20160ca01919/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java#L826
- https://stackoverflow.com/a/67636834
- https://github.com/dteleguin/beercloak/blob/master/beercloak-module/src/main/java/beercloak/providers/BeerResourceProvider.java
- https://github.com/dteleguin/beercloak/blob/master/beercloak-module/src/main/java/beercloak/resources/AbstractAdminResource.java
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<artifactId>actions-token</artifactId>
<groupId>org.jboss.aerogear</groupId>
<packaging>jar</packaging>
<version>0.0.4</version>
<version>0.0.5</version>

<properties>
<java.version>11</java.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@

package com.github.maxime1907.keycloak.actions.token;

import java.util.LinkedList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.TokenCategory;
import org.keycloak.authentication.actiontoken.execactions.ExecuteActionsActionToken;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorResponse;

import com.google.gson.Gson;

import lombok.extern.jbosslog.JBossLog;

@JBossLog
public class ActionsTokenResource {

private final KeycloakSession session;

public ActionsTokenResource(KeycloakSession session) {
this.session = session;
}

@POST
@NoCache
@Path("generate")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response getActionToken(
String jsonString,
@Context UriInfo uriInfo) {
// if (this.auth == null || this.auth.getToken() == null) {
// throw new NotAuthorizedException("Bearer");
// }

ActionTokenRequest actionTokenRequest = null;
try {
Gson gson = new Gson();
actionTokenRequest = gson.fromJson(jsonString, ActionTokenRequest.class);
} catch (IllegalArgumentException cause) {
throw new WebApplicationException(
ErrorResponse.error("Invalid json input.", Status.BAD_REQUEST));
}

log.debugf("%s", actionTokenRequest.userId);
log.debugf("%s", actionTokenRequest.redirectUri);
log.debugf("%s", actionTokenRequest.clientId);
log.debugf("%s", actionTokenRequest.actions);
log.debugf("%s", actionTokenRequest.redirectUriValidate);
log.debugf("%s", actionTokenRequest.lifespan);

KeycloakContext context = session.getContext();

if (actionTokenRequest.redirectUri != null && actionTokenRequest.clientId == null) {
throw new WebApplicationException(
ErrorResponse.error("Client id missing", Status.BAD_REQUEST));
}

if (actionTokenRequest.clientId == null) {
actionTokenRequest.clientId = Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
}

ClientModel client = assertValidClient(actionTokenRequest.clientId, context);
if (actionTokenRequest.redirectUri != null && actionTokenRequest.redirectUriValidate != null && actionTokenRequest.redirectUriValidate)
assertValidRedirectUri(actionTokenRequest.redirectUri, client);

// /auth/admin/master/console/#/realms/master/token-settings User-Initiated Action Lifespan
int validityInSecs = context.getRealm().getActionTokenGeneratedByAdminLifespan();
if (actionTokenRequest.lifespan != null)
validityInSecs = actionTokenRequest.lifespan;
int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;

// Can parameterize this as well
List<String> requiredActions = new LinkedList<String>();

try {
for (int i = 0; i < actionTokenRequest.actions.size(); i++) {
String requiredActionName = actionTokenRequest.actions.get(i);
RequiredAction requiredAction = RequiredAction.valueOf(requiredActionName);
requiredActions.add(requiredAction.name());
}
} catch (IllegalArgumentException cause) {
throw new WebApplicationException(
ErrorResponse.error("Invalid requiredAction.", Status.BAD_REQUEST));
}

ExecuteActionsActionToken token = new ExecuteActionsActionToken(
actionTokenRequest.userId,
absoluteExpirationInSecs,
requiredActions,
actionTokenRequest.redirectUri,
actionTokenRequest.clientId
){
@Override
public TokenCategory getCategory() {
return TokenCategory.ADMIN;
}
};

String tokenKey = token.serialize(
session,
context.getRealm(),
uriInfo
);

Gson gson = new Gson();
ActionToken actionToken = new ActionToken(tokenKey);
String jsonInString = gson.toJson(actionToken);
return Response.status(200).entity(jsonInString).build();
}

private void assertValidRedirectUri(String redirectUri, ClientModel client) {
String redirect = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
if (redirect == null) {
throw new WebApplicationException(
ErrorResponse.error("Invalid redirect uri.", Status.BAD_REQUEST));
}
}

private ClientModel assertValidClient(String clientId, KeycloakContext context) {
ClientModel client = context.getRealm().getClientByClientId(clientId);
if (client == null) {
log.debugf("Client %s doesn't exist", clientId);
throw new WebApplicationException(
ErrorResponse.error("Client doesn't exist", Status.BAD_REQUEST));
}
if (!client.isEnabled()) {
log.debugf("Client %s is not enabled", clientId);
throw new WebApplicationException(
ErrorResponse.error("Client is not enabled", Status.BAD_REQUEST));
}
return client;
}
}
Original file line number Diff line number Diff line change
@@ -1,155 +1,29 @@
package com.github.maxime1907.keycloak.actions.token;

import java.util.LinkedList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;

import org.keycloak.TokenCategory;
import org.keycloak.authentication.actiontoken.execactions.ExecuteActionsActionToken;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager.AuthResult;
import org.keycloak.services.resource.RealmResourceProvider;

import com.google.gson.Gson;

import lombok.extern.jbosslog.JBossLog;

@JBossLog
public class ActionsTokenResourceProvider implements RealmResourceProvider {

private final KeycloakSession session;
// The ID of the provider is also used as the name of the endpoint
public final static String ID = "actions-token";

private KeycloakSession session;
private AuthResult auth;

public ActionsTokenResourceProvider(KeycloakSession session) {
this.session = session;
}

@POST
@Path("generate")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response getActionToken(
String jsonString,
@Context UriInfo uriInfo) {
ActionTokenRequest actionTokenRequest = null;
try {
Gson gson = new Gson();
actionTokenRequest = gson.fromJson(jsonString, ActionTokenRequest.class);
} catch (IllegalArgumentException cause) {
throw new WebApplicationException(
ErrorResponse.error("Invalid json input.", Status.BAD_REQUEST));
}

log.debugf("%s", actionTokenRequest.userId);
log.debugf("%s", actionTokenRequest.redirectUri);
log.debugf("%s", actionTokenRequest.clientId);
log.debugf("%s", actionTokenRequest.actions);
log.debugf("%s", actionTokenRequest.redirectUriValidate);
log.debugf("%s", actionTokenRequest.lifespan);

KeycloakContext context = session.getContext();
RealmModel realm = context.getRealm();

if (actionTokenRequest.redirectUri != null && actionTokenRequest.clientId == null) {
throw new WebApplicationException(
ErrorResponse.error("Client id missing", Status.BAD_REQUEST));
}

if (actionTokenRequest.clientId == null) {
actionTokenRequest.clientId = Constants.ACCOUNT_MANAGEMENT_CLIENT_ID;
}

ClientModel client = assertValidClient(actionTokenRequest.clientId, realm);
if (actionTokenRequest.redirectUri != null && actionTokenRequest.redirectUriValidate != null && actionTokenRequest.redirectUriValidate)
assertValidRedirectUri(actionTokenRequest.redirectUri, client);

// /auth/admin/master/console/#/realms/master/token-settings User-Initiated Action Lifespan
int validityInSecs = realm.getActionTokenGeneratedByAdminLifespan();
if (actionTokenRequest.lifespan != null)
validityInSecs = actionTokenRequest.lifespan;
int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;

// Can parameterize this as well
List<String> requiredActions = new LinkedList<String>();

try {
for (int i = 0; i < actionTokenRequest.actions.size(); i++) {
String requiredActionName = actionTokenRequest.actions.get(i);
RequiredAction requiredAction = RequiredAction.valueOf(requiredActionName);
requiredActions.add(requiredAction.name());
}
} catch (IllegalArgumentException cause) {
throw new WebApplicationException(
ErrorResponse.error("Invalid requiredAction.", Status.BAD_REQUEST));
}

ExecuteActionsActionToken token = new ExecuteActionsActionToken(
actionTokenRequest.userId,
absoluteExpirationInSecs,
requiredActions,
actionTokenRequest.redirectUri,
actionTokenRequest.clientId
){
@Override
public TokenCategory getCategory() {
return TokenCategory.ADMIN;
}
};

String tokenKey = token.serialize(
session,
realm,
uriInfo
);

Gson gson = new Gson();
ActionToken actionToken = new ActionToken(tokenKey);
String jsonInString = gson.toJson(actionToken);
return Response.status(200).entity(jsonInString).build();
}

private void assertValidRedirectUri(String redirectUri, ClientModel client) {
String redirect = RedirectUtils.verifyRedirectUri(session, redirectUri, client);
if (redirect == null) {
throw new WebApplicationException(
ErrorResponse.error("Invalid redirect uri.", Status.BAD_REQUEST));
}
}

private ClientModel assertValidClient(String clientId, RealmModel realm) {
ClientModel client = realm.getClientByClientId(clientId);
if (client == null) {
log.debugf("Client %s doesn't exist", clientId);
throw new WebApplicationException(
ErrorResponse.error("Client doesn't exist", Status.BAD_REQUEST));
}
if (!client.isEnabled()) {
log.debugf("Client %s is not enabled", clientId);
throw new WebApplicationException(
ErrorResponse.error("Client is not enabled", Status.BAD_REQUEST));
}
return client;
this.auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate();
}

@Override
public Object getResource() {
return this;
// RealmModel realm = session.getContext().getRealm();
ActionsTokenResource actionToken = new ActionsTokenResource(this.session);
return actionToken;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,9 @@

public class ActionsTokenResourceProviderFactory implements RealmResourceProviderFactory {

private ActionsTokenResourceProvider actionsTokenResourceProvider;

@Override
public RealmResourceProvider create(KeycloakSession keycloakSession) {
if (actionsTokenResourceProvider == null) {
actionsTokenResourceProvider = new ActionsTokenResourceProvider(keycloakSession);
}
return actionsTokenResourceProvider;
return new ActionsTokenResourceProvider(keycloakSession);
}

@Override
Expand All @@ -34,6 +29,6 @@ public void close() {

@Override
public String getId() {
return "actions-token";
return ActionsTokenResourceProvider.ID;
}
}

0 comments on commit 295726a

Please sign in to comment.