use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.
the class BackchannelAuthenticationCallbackEndpoint method verifyAuthenticationRequest.
private BackchannelAuthCallbackContext verifyAuthenticationRequest(HttpHeaders headers) {
String rawBearerToken = AppAuthManager.extractAuthorizationHeaderTokenOrReturnNull(headers);
if (rawBearerToken == null) {
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.UNAUTHORIZED);
}
AccessToken bearerToken;
try {
bearerToken = TokenVerifier.createWithoutSignature(session.tokens().decode(rawBearerToken, AccessToken.class)).withDefaultChecks().realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())).checkActive(true).audience(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())).verify().getToken();
} catch (Exception e) {
event.error(Errors.INVALID_TOKEN);
// authentication channel id format is invalid or it has already been used
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.FORBIDDEN);
}
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
OAuth2DeviceCodeModel deviceCode = store.getByUserCode(realm, bearerToken.getId());
if (deviceCode == null) {
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.FORBIDDEN);
}
if (!deviceCode.isPending()) {
cancelRequest(bearerToken.getId());
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.FORBIDDEN);
}
ClientModel issuedFor = realm.getClientByClientId(bearerToken.getIssuedFor());
if (issuedFor == null || !issuedFor.isEnabled()) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Invalid token recipient", Response.Status.BAD_REQUEST);
}
if (!deviceCode.getClientId().equals(issuedFor.getClientId())) {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Token recipient mismatch", Response.Status.BAD_REQUEST);
}
session.getContext().setClient(issuedFor);
event.client(issuedFor);
return new BackchannelAuthCallbackContext(bearerToken, deviceCode);
}
use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.
the class BackchannelAuthenticationCallbackEndpoint method processAuthenticationChannelResult.
@Path("/")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response processAuthenticationChannelResult(AuthenticationChannelResponse response) {
event.event(EventType.LOGIN);
BackchannelAuthCallbackContext ctx = verifyAuthenticationRequest(httpRequest.getHttpHeaders());
AccessToken bearerToken = ctx.bearerToken;
OAuth2DeviceCodeModel deviceModel = ctx.deviceModel;
Status status = response.getStatus();
if (status == null) {
event.error(Errors.INVALID_REQUEST);
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Invalid authentication status", Response.Status.BAD_REQUEST);
}
switch(status) {
case SUCCEED:
approveRequest(bearerToken, response.getAdditionalParams());
break;
case CANCELLED:
case UNAUTHORIZED:
denyRequest(bearerToken, status);
break;
}
// Call the notification endpoint
ClientModel client = session.getContext().getClient();
CibaConfig cibaConfig = realm.getCibaPolicy();
if (cibaConfig.getBackchannelTokenDeliveryMode(client).equals(CibaConfig.CIBA_PING_MODE)) {
sendClientNotificationRequest(client, cibaConfig, deviceModel);
}
return Response.ok(MediaType.APPLICATION_JSON_TYPE).build();
}
use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.
the class InfinispanOAuth2DeviceTokenStoreProvider method deny.
@Override
public boolean deny(RealmModel realm, String userCode) {
try {
OAuth2DeviceCodeModel deviceCode = findDeviceCodeByUserCode(realm, userCode);
if (deviceCode == null) {
return false;
}
OAuth2DeviceCodeModel denied = deviceCode.deny();
BasicCache<String, ActionTokenValueEntity> cache = codeCache.get();
cache.replace(denied.serializeKey(), new ActionTokenValueEntity(denied.toMap()));
return true;
} catch (HotRodClientException re) {
// In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place.
if (logger.isDebugEnabled()) {
logger.debugf(re, "Failed when denying device user code %s", userCode);
}
return false;
}
}
use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.
the class InfinispanOAuth2DeviceTokenStoreProvider method approve.
@Override
public boolean approve(RealmModel realm, String userCode, String userSessionId, Map<String, String> additionalParams) {
try {
OAuth2DeviceCodeModel deviceCode = findDeviceCodeByUserCode(realm, userCode);
if (deviceCode == null) {
return false;
}
OAuth2DeviceCodeModel approved = deviceCode.approve(userSessionId, additionalParams);
// Update the device code with approved status
BasicCache<String, ActionTokenValueEntity> cache = codeCache.get();
cache.replace(approved.serializeKey(), new ActionTokenValueEntity(approved.toMap()));
return true;
} catch (HotRodClientException re) {
// In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place.
if (logger.isDebugEnabled()) {
logger.debugf(re, "Failed when verifying device user code %s", userCode);
}
return false;
}
}
use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.
the class BackchannelAuthenticationEndpoint method storeAuthenticationRequest.
/**
* TODO: Leverage the device code storage for tracking authentication requests. Not sure if we need a specific storage,
* but probably make the {@link OAuth2DeviceTokenStoreProvider} more generic for ciba, device, or any other use case
* that relies on cross-references for unsolicited user authentication requests from devices.
*/
private void storeAuthenticationRequest(CIBAAuthenticationRequest request, CibaConfig cibaConfig, String authReqId) {
ClientModel client = request.getClient();
int expiresIn = cibaConfig.getExpiresIn();
int poolingInterval = cibaConfig.getPoolingInterval();
String cibaMode = cibaConfig.getBackchannelTokenDeliveryMode(client);
// Set authReqId just for the ping mode as it is relatively big and not necessarily needed in the infinispan cache for the "poll" mode
if (!CibaConfig.CIBA_PING_MODE.equals(cibaMode)) {
authReqId = null;
}
OAuth2DeviceCodeModel deviceCode = OAuth2DeviceCodeModel.create(realm, client, request.getId(), request.getScope(), null, expiresIn, poolingInterval, request.getClientNotificationToken(), authReqId, Collections.emptyMap(), null, null);
String authResultId = request.getAuthResultId();
OAuth2DeviceUserCodeModel userCode = new OAuth2DeviceUserCodeModel(realm, deviceCode.getDeviceCode(), authResultId);
// To inform "expired_token" to the client, the lifespan of the cache provider is longer than device code
int lifespanSeconds = expiresIn + poolingInterval + 10;
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
store.put(deviceCode, userCode, lifespanSeconds);
}
Aggregations