KEYCLOAK-1129 Implicit flow and Hybrid flow support

This commit is contained in:
mposolda 2015-11-24 14:26:41 +01:00
parent 8d2e4c0316
commit ef80b64d1c
25 changed files with 623 additions and 141 deletions

View file

@ -341,6 +341,17 @@ public class KeycloakUriBuilder {
return this;
}
/**
* Set fragment, but not encode it. It assumes that given fragment was already properly encoded
*
* @param fragment
* @return
*/
public KeycloakUriBuilder encodedFragment(String fragment) {
this.fragment = fragment;
return this;
}
/**
* Only replace path params in path of URI. This changes state of URIBuilder.
*

View file

@ -28,6 +28,7 @@ public class RefreshToken extends AccessToken {
this.subject = token.subject;
this.issuedFor = token.issuedFor;
this.sessionState = token.sessionState;
this.nonce = token.nonce;
if (token.realmAccess != null) {
realmAccess = token.realmAccess.clone();
}

View file

@ -11,6 +11,7 @@ public interface Details {
String CODE_ID = "code_id";
String REDIRECT_URI = "redirect_uri";
String RESPONSE_TYPE = "response_type";
String RESPONSE_MODE = "response_mode";
String AUTH_TYPE = "auth_type";
String AUTH_METHOD = "auth_method";
String IDENTITY_PROVIDER = "identity_provider";

View file

@ -106,7 +106,13 @@
event('Auth Logout');
};
keycloak.init().success(function(authenticated) {
// Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow too in admin console
var initOptions = {
responseMode: 'fragment',
flow: 'standard'
};
keycloak.init(initOptions).success(function(authenticated) {
output('Init Success (' + (authenticated ? 'Authenticated' : 'Not Authenticated') + ')');
}).error(function() {
output('Init Error');

View file

@ -176,7 +176,8 @@ failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
standardFlowDisabledMessage=Client is not allowed to initiate browser login because standard flow is disabled for the client.
standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
invalidRedirectUriMessage=Invalid redirect uri
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
invlidRequesterMessage=Invalid requester

View file

@ -36,7 +36,40 @@
if (initOptions.onLoad === 'login-required') {
kc.loginRequired = true;
}
if (initOptions.responseMode) {
if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') {
kc.responseMode = initOptions.responseMode;
} else {
throw 'Invalid value for responseMode';
}
}
if (initOptions.flow) {
switch (initOptions.flow) {
case 'standard':
kc.responseType = 'code';
break;
case 'implicit':
kc.responseType = 'id_token token refresh_token';
break;
case 'hybrid':
kc.responseType = 'code id_token token refresh_token';
break;
default:
throw 'Invalid value for flow';
}
}
}
if (!kc.responseMode) {
kc.responseMode = 'fragment';
}
if (!kc.responseType) {
kc.responseType = 'code';
}
console.log('responseMode=' + kc.responseMode + ', responseType=' + kc.responseType);
var promise = createPromise();
@ -132,13 +165,14 @@
kc.createLoginUrl = function(options) {
var state = createUUID();
var nonce = createUUID();
var redirectUri = adapter.redirectUri(options);
if (options && options.prompt) {
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
}
sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) });
sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
var action = 'auth';
if (options && options.action == 'register') {
@ -150,7 +184,9 @@
+ '?client_id=' + encodeURIComponent(kc.clientId)
+ '&redirect_uri=' + encodeURIComponent(redirectUri)
+ '&state=' + encodeURIComponent(state)
+ '&response_type=code';
+ '&nonce=' + encodeURIComponent(nonce)
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
+ '&response_type=' + encodeURIComponent(kc.responseType);
if (options && options.prompt) {
url += '&prompt=' + encodeURIComponent(options.prompt);
@ -394,6 +430,8 @@
var error = oauth.error;
var prompt = oauth.prompt;
var timeLocal = new Date().getTime();
if (code) {
var params = 'code=' + code + '&grant_type=authorization_code';
var url = getRealmUrl() + '/protocol/openid-connect/token';
@ -412,20 +450,12 @@
req.withCredentials = true;
var timeLocal = new Date().getTime();
req.onreadystatechange = function() {
if (req.readyState == 4) {
if (req.status == 200) {
timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess();
authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'])
} else {
kc.onAuthError && kc.onAuthError();
promise && promise.setError();
@ -441,7 +471,31 @@
} else {
promise && promise.setSuccess();
}
} else if (oauth.access_token || oauth.id_token || oauth.refresh_token) {
authSuccess(oauth.access_token, oauth.refresh_token, oauth.id_token);
}
function authSuccess(accessToken, refreshToken, idToken) {
timeLocal = (timeLocal + new Date().getTime()) / 2;
setToken(accessToken, refreshToken, idToken);
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
console.log('invalid nonce!');
kc.clearToken();
promise && promise.setError();
} else {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess();
}
}
}
function loadConfig(url) {
@ -597,46 +651,15 @@
}
function parseCallback(url) {
if (url.indexOf('?') != -1) {
var oauth = {};
oauth.newUrl = url.split('?')[0];
var paramString = url.split('?')[1];
var fragIndex = paramString.indexOf('#');
if (fragIndex != -1) {
paramString = paramString.substring(0, fragIndex);
}
var params = paramString.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
switch (decodeURIComponent(p[0])) {
case 'code':
oauth.code = p[1];
break;
case 'error':
oauth.error = p[1];
break;
case 'state':
oauth.state = decodeURIComponent(p[1]);
break;
case 'redirect_fragment':
oauth.fragment = decodeURIComponent(p[1]);
break;
case 'prompt':
oauth.prompt = p[1];
break;
default:
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1];
break;
}
}
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) {
if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
delete sessionStorage.oauthState;
oauth.redirectUri = sessionState.redirectUri;
oauth.storedNonce = sessionState.nonce;
if (oauth.fragment) {
oauth.newUrl += '#' + oauth.fragment;
@ -645,7 +668,6 @@
return oauth;
}
}
}
function createPromise() {
var p = {
@ -907,6 +929,105 @@
throw 'invalid adapter type: ' + type;
}
var CallbackParser = function(uriToParse, responseMode) {
if (!(this instanceof CallbackParser)) {
return new CallbackParser(uriToParse, responseMode);
}
var parser = this;
var initialParse = function() {
var baseUri = null;
var queryString = null;
var fragmentString = null;
var questionMarkIndex = uriToParse.indexOf("?");
var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1);
if (questionMarkIndex == -1 && fragmentIndex == -1) {
baseUri = uriToParse;
} else if (questionMarkIndex != -1) {
baseUri = uriToParse.substring(0, questionMarkIndex);
queryString = uriToParse.substring(questionMarkIndex + 1);
if (fragmentIndex != -1) {
fragmentIndex = queryString.indexOf("#");
fragmentString = queryString.substring(fragmentIndex + 1);
queryString = queryString.substring(0, fragmentIndex);
}
} else {
baseUri = uriToParse.substring(0, fragmentIndex);
fragmentString = uriToParse.substring(fragmentIndex + 1);
}
return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString };
}
var parseParams = function(paramString) {
var result = {};
var params = paramString.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
var paramName = decodeURIComponent(p[0]);
var paramValue = decodeURIComponent(p[1]);
result[paramName] = paramValue;
}
return result;
}
var handleQueryParam = function(paramName, paramValue, oauth) {
var supportedOAuthParams = [ 'code', 'error', 'state' ];
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
if (paramName === supportedOAuthParams[i]) {
oauth[paramName] = paramValue;
return true;
}
}
return false;
}
parser.parseUri = function() {
var parsedUri = initialParse();
var queryParams = {};
if (parsedUri.queryString) {
queryParams = parseParams(parsedUri.queryString);
}
var oauth = { newUrl: parsedUri.baseUri };
for (var param in queryParams) {
switch (param) {
case 'redirect_fragment':
oauth.fragment = queryParams[param];
break;
case 'prompt':
oauth.prompt = queryParams[param];
break;
default:
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
}
break;
}
}
if (responseMode === 'fragment') {
var fragmentParams = {};
if (parsedUri.fragmentString) {
fragmentParams = parseParams(parsedUri.fragmentString);
}
for (var param in fragmentParams) {
oauth[param] = fragmentParams[param];
}
}
console.log("OAUTH: ");
console.log(oauth);
return oauth;
}
}
}
if ( typeof module === "object" && module && typeof module.exports === "object" ) {

View file

@ -470,7 +470,8 @@ public class AuthenticationProcessor {
LoginProtocol protocol = getSession().getProvider(LoginProtocol.class, getClientSession().getAuthMethod());
protocol.setRealm(getRealm())
.setHttpHeaders(getHttpRequest().getHttpHeaders())
.setUriInfo(getUriInfo());
.setUriInfo(getUriInfo())
.setEventBuilder(event);
Response response = protocol.sendError(getClientSession(), Error.CANCELLED_BY_USER);
forceChallenge(response);
}
@ -808,7 +809,7 @@ public class AuthenticationProcessor {
public Response finishAuthentication() {
event.success();
RealmModel realm = clientSession.getRealm();
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection);
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event);
}

View file

@ -33,6 +33,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ResourceAdminManager;
@ -62,6 +66,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
public static final String ISSUER = "iss";
public static final String RESPONSE_MODE_PARAM = "response_mode";
private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
protected KeycloakSession session;
@ -74,6 +80,9 @@ public class OIDCLoginProtocol implements LoginProtocol {
protected EventBuilder event;
protected OIDCResponseType responseType;
protected OIDCResponseMode responseMode;
public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
this.session = session;
this.realm = realm;
@ -86,6 +95,15 @@ public class OIDCLoginProtocol implements LoginProtocol {
}
private void setupResponseTypeAndMode(ClientSessionModel clientSession) {
String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
String responseMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
this.responseType = OIDCResponseType.parse(responseType);
this.responseMode = OIDCResponseMode.parse(responseMode, this.responseType);
this.event.detail(Details.RESPONSE_TYPE, responseType);
this.event.detail(Details.RESPONSE_MODE, this.responseMode.toString().toLowerCase());
}
@Override
public OIDCLoginProtocol setSession(KeycloakSession session) {
this.session = session;
@ -116,32 +134,70 @@ public class OIDCLoginProtocol implements LoginProtocol {
return this;
}
@Override
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
ClientSessionModel clientSession = accessCode.getClientSession();
setupResponseTypeAndMode(clientSession);
String redirect = clientSession.getRedirectUri();
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
log.debugv("redirectAccessCode: state: {0}", state);
if (state != null)
redirectUri.queryParam(OAuth2Constants.STATE, state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
redirectUri.addParam(OAuth2Constants.STATE, state);
return location.build();
// Standard or hybrid flow
if (responseType.hasResponseType(OIDCResponseType.CODE)) {
accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
redirectUri.addParam(OAuth2Constants.CODE, accessCode.getCode());
}
// Implicit or hybrid flow
if (responseType.hasResponseType(OIDCResponseType.TOKEN) || responseType.hasResponseType(OIDCResponseType.ID_TOKEN) || responseType.hasResponseType(OIDCResponseType.REFRESH_TOKEN)) {
TokenManager tokenManager = new TokenManager();
AccessTokenResponse res = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSession)
.generateAccessToken()
.generateRefreshToken()
.generateIDToken()
.build();
if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
redirectUri.addParam("id_token", res.getIdToken());
}
if (responseType.hasResponseType(OIDCResponseType.TOKEN)) {
redirectUri.addParam("access_token", res.getToken());
redirectUri.addParam("token_type", res.getTokenType());
redirectUri.addParam("session-state", res.getSessionState());
redirectUri.addParam("expires_in", String.valueOf(res.getExpiresIn()));
}
// Not OIDC standard, but supported
if (responseType.hasResponseType(OIDCResponseType.REFRESH_TOKEN)) {
redirectUri.addParam("refresh_token", res.getRefreshToken());
redirectUri.addParam("refresh_expires_in", String.valueOf(res.getRefreshExpiresIn()));
}
redirectUri.addParam("not-before-policy", String.valueOf(res.getNotBeforePolicy()));
}
return redirectUri.build();
}
@Override
public Response sendError(ClientSessionModel clientSession, Error error) {
setupResponseTypeAndMode(clientSession);
String redirect = clientSession.getRedirectUri();
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, translateError(error));
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode).addParam(OAuth2Constants.ERROR, translateError(error));
if (state != null)
redirectUri.queryParam(OAuth2Constants.STATE, state);
redirectUri.addParam(OAuth2Constants.STATE, state);
session.sessions().removeClientSession(realm, clientSession);
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
return location.build();
return redirectUri.build();
}
private String translateError(Error error) {
@ -161,10 +217,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
if (!(clientSession.getClient() instanceof ClientModel))
return;
ClientModel app = clientSession.getClient();
new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession);
ClientModel client = clientSession.getClient();
new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, client, clientSession);
}
@Override

View file

@ -52,7 +52,7 @@ import java.util.Map;
import java.util.Set;
/**
* Stateful object that creates tokens and manages oauth access codes
* Stateless object that creates tokens and manages oauth access codes
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $

View file

@ -1,19 +1,11 @@
package org.keycloak.protocol.oidc.endpoints;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.common.ClientConnection;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@ -24,12 +16,12 @@ import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.Urls;
@ -55,11 +47,13 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private ClientSessionModel clientSession;
private Action action;
private OIDCResponseType parsedResponseType;
private String clientId;
private String redirectUri;
private String redirectUriParam;
private String responseType;
private String responseMode;
private String state;
private String scope;
private String loginHint;
@ -80,6 +74,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
responseMode = params.getFirst(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
@ -90,8 +85,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
checkSsl();
checkRealm();
checkClient();
checkResponseType();
checkClient();
checkRedirectUri();
createClientSession();
@ -172,11 +167,17 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
throw new ErrorPageException(session, Messages.BEARER_ONLY);
}
if (!client.isStandardFlowEnabled()) {
if ((parsedResponseType.hasResponseType(OIDCResponseType.CODE) || parsedResponseType.hasResponseType(OIDCResponseType.NONE)) && !client.isStandardFlowEnabled()) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorPageException(session, Messages.STANDARD_FLOW_DISABLED);
}
if ((parsedResponseType.hasResponseType(OIDCResponseType.TOKEN) || parsedResponseType.hasResponseType(OIDCResponseType.ID_TOKEN) || parsedResponseType.hasResponseType(OIDCResponseType.REFRESH_TOKEN))
&& !client.isImplicitFlowEnabled()) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorPageException(session, Messages.IMPLICIT_FLOW_DISABLED);
}
session.getContext().setClient(client);
}
@ -192,14 +193,32 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
event.detail(Details.RESPONSE_TYPE, responseType);
if (responseType.equals(OAuth2Constants.CODE)) {
try {
parsedResponseType = OIDCResponseType.parse(responseType);
if (action == null) {
action = Action.CODE;
}
} else {
} catch (IllegalArgumentException iae) {
logger.error(iae.getMessage());
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
}
try {
OIDCResponseMode parsedResponseMode = OIDCResponseMode.parse(responseMode, parsedResponseType);
event.detail(Details.RESPONSE_MODE, parsedResponseMode.toString().toLowerCase());
// Disallowed by OIDC specs
if (parsedResponseType.isImplicitOrHybridFlow() && parsedResponseMode == OIDCResponseMode.QUERY) {
logger.error("Response_mode 'query' not allowed for implicit or hybrid flow");
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
}
} catch (IllegalArgumentException iae) {
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
}
}
private void checkRedirectUri() {
@ -228,6 +247,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt);
if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint);
if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode);
}
private Response buildAuthorizationCodeAuthorizationResponse() {

View file

@ -327,7 +327,7 @@ public class TokenEndpoint {
}
public Response buildResourceOwnerPasswordCredentialsGrant() {
event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD);
if (client.isConsentRequired()) {
event.error(Errors.CONSENT_DENIED);
@ -393,7 +393,7 @@ public class TokenEndpoint {
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
}
event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
event.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
UserModel clientUser = session.users().getUserByServiceAccountClient(client);

View file

@ -0,0 +1,149 @@
package org.keycloak.protocol.oidc.utils;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.keycloak.common.util.Encode;
import org.keycloak.common.util.KeycloakUriBuilder;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class OIDCRedirectUriBuilder {
protected final KeycloakUriBuilder uriBuilder;
protected OIDCRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
this.uriBuilder = uriBuilder;
}
public abstract OIDCRedirectUriBuilder addParam(String paramName, String paramValue);
public abstract Response build();
public static OIDCRedirectUriBuilder fromUri(String baseUri, OIDCResponseMode responseMode) {
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(baseUri);
switch (responseMode) {
case QUERY: return new QueryRedirectUriBuilder(uriBuilder);
case FRAGMENT: return new FragmentRedirectUriBuilder(uriBuilder);
case FORM_POST: return new FormPostRedirectUriBuilder(uriBuilder);
}
throw new IllegalStateException("Not possible to end here");
}
// Impl subclasses
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
public static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
protected QueryRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
super(uriBuilder);
}
@Override
public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
uriBuilder.queryParam(paramName, paramValue);
return this;
}
@Override
public Response build() {
URI redirectUri = uriBuilder.build();
Response.ResponseBuilder location = Response.status(302).location(redirectUri);
return location.build();
}
}
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
public static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
private StringBuilder fragment;
protected FragmentRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
super(uriBuilder);
}
@Override
public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
String param = paramName + "=" + Encode.encodeQueryParam(paramValue);
if (fragment == null) {
fragment = new StringBuilder(param);
} else {
fragment.append("&").append(param);
}
return this;
}
@Override
public Response build() {
if (fragment != null) {
uriBuilder.encodedFragment(fragment.toString());
}
URI redirectUri = uriBuilder.build();
Response.ResponseBuilder location = Response.status(302).location(redirectUri);
return location.build();
}
}
// http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
public static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
private Map<String, String> params = new HashMap<>();
protected FormPostRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
super(uriBuilder);
}
@Override
public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
params.put(paramName, Encode.encodeQueryParam(paramValue));
return this;
}
@Override
public Response build() {
StringBuilder builder = new StringBuilder();
URI redirectUri = uriBuilder.build();
builder.append("<HTML>");
builder.append(" <HEAD>");
builder.append(" <TITLE>OIDC Form_Post Response</TITLE>");
builder.append(" </HEAD>");
builder.append(" <BODY Onload=\"document.forms[0].submit()\">");
builder.append(" <FORM METHOD=\"POST\" ACTION=\"" + redirectUri.toString() + "\">");
for (Map.Entry<String, String> param : params.entrySet()) {
builder.append(" <INPUT TYPE=\"HIDDEN\" NAME=\"").append(param.getKey())
.append("\" VALUE=\"").append(param.getValue()).append("\" />");
}
builder.append(" <NOSCRIPT>");
builder.append(" <P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>");
builder.append(" <INPUT name=\"continue\" TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
builder.append(" </NOSCRIPT>");
builder.append(" </FORM>");
builder.append(" </BODY>");
builder.append("</HTML>");
return Response.status(Response.Status.OK)
.type(MediaType.TEXT_HTML_TYPE)
.entity(builder.toString()).build();
}
}
}

View file

@ -0,0 +1,25 @@
package org.keycloak.protocol.oidc.utils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public enum OIDCResponseMode {
QUERY, FRAGMENT, FORM_POST;
public static OIDCResponseMode parse(String responseMode, OIDCResponseType responseType) {
if (responseMode == null) {
return getDefaultResponseMode(responseType);
} else {
return Enum.valueOf(OIDCResponseMode.class, responseMode.toUpperCase());
}
}
private static OIDCResponseMode getDefaultResponseMode(OIDCResponseType responseType) {
if (responseType.isImplicitOrHybridFlow()) {
return OIDCResponseMode.FRAGMENT;
} else {
return OIDCResponseMode.QUERY;
}
}
}

View file

@ -0,0 +1,72 @@
package org.keycloak.protocol.oidc.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class OIDCResponseType {
public static final String CODE = OIDCLoginProtocol.CODE_PARAM;
public static final String TOKEN = "token";
public static final String ID_TOKEN = "id_token";
public static final String REFRESH_TOKEN = "refresh_token"; // Not officially supported by OIDC
public static final String NONE = "none";
private static final List<String> ALLOWED_RESPONSE_TYPES = Arrays.asList(CODE, TOKEN, ID_TOKEN, REFRESH_TOKEN, NONE);
private final List<String> responseTypes;
private OIDCResponseType(List<String> responseTypes) {
this.responseTypes = responseTypes;
}
public static OIDCResponseType parse(String responseTypeParam) {
if (responseTypeParam == null) {
throw new IllegalStateException("response_type is null");
}
String[] responseTypes = responseTypeParam.trim().split(" ");
List<String> allowedTypes = new ArrayList<>();
for (String current : responseTypes) {
if (ALLOWED_RESPONSE_TYPES.contains(current)) {
allowedTypes.add(current);
} else {
throw new IllegalStateException("Unsupported response_type: " + responseTypeParam);
}
}
return new OIDCResponseType(allowedTypes);
}
public boolean hasResponseType(String responseType) {
return responseTypes.contains(responseType);
}
public boolean isImplicitOrHybridFlow() {
return hasResponseType(TOKEN) || hasResponseType(ID_TOKEN) || hasResponseType(REFRESH_TOKEN);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (String responseType : responseTypes) {
if (!first) {
builder.append(" ");
} else {
first = false;
}
builder.append(responseType);
}
return builder.toString();
}
}

View file

@ -380,7 +380,8 @@ public class AuthenticationManager {
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
ClientSessionModel clientSession,
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection) {
HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
EventBuilder event) {
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) {
@ -407,7 +408,8 @@ public class AuthenticationManager {
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setHttpHeaders(request.getHttpHeaders())
.setUriInfo(uriInfo);
.setUriInfo(uriInfo)
.setEventBuilder(event);
RestartLoginCookie.expireRestartCookie(realm, clientConnection, uriInfo);
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
@ -429,7 +431,7 @@ public class AuthenticationManager {
}
event.success();
RealmModel realm = clientSession.getRealm();
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection, event);
}
@ -522,9 +524,11 @@ public class AuthenticationManager {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo());
.setUriInfo(context.getUriInfo())
.setEventBuilder(event);
Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
return response;
}
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());

View file

@ -112,6 +112,8 @@ public class Messages {
public static final String STANDARD_FLOW_DISABLED = "standardFlowDisabledMessage";
public static final String IMPLICIT_FLOW_DISABLED = "implicitFlowDisabledMessage";
public static final String INVALID_REDIRECT_URI = "invalidRedirectUriMessage";
public static final String UNSUPPORTED_NAME_ID_FORMAT = "unsupportedNameIdFormatMessage";

View file

@ -59,6 +59,8 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
@ -546,7 +548,7 @@ public class LoginActionsService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processConsent(final MultivaluedMap<String, String> formData) {
event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
event.event(EventType.LOGIN);
if (!checkSsl()) {
@ -561,38 +563,28 @@ public class LoginActionsService {
return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE);
}
ClientSessionModel clientSession = accessCode.getClientSession();
event.detail(Details.CODE_ID, clientSession.getId());
String redirect = clientSession.getRedirectUri();
initEvent(clientSession);
UserSessionModel userSession = clientSession.getUserSession();
UserModel user = userSession.getUser();
ClientModel client = clientSession.getClient();
event.client(client)
.user(user)
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.REDIRECT_URI, redirect);
event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
event.detail(Details.USERNAME, userSession.getLoginUsername());
if (userSession.isRememberMe()) {
event.detail(Details.REMEMBER_ME, "true");
}
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
}
event.session(userSession);
if (formData.containsKey("cancel")) {
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setHttpHeaders(headers)
.setUriInfo(uriInfo);
.setUriInfo(uriInfo)
.setEventBuilder(event);
Response response = protocol.sendError(clientSession, Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
return protocol.sendError(clientSession, Error.CONSENT_DENIED);
return response;
}
UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
@ -613,7 +605,7 @@ public class LoginActionsService {
event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
event.success();
return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event);
}
@Path("email-verification")
@ -727,24 +719,29 @@ public class LoginActionsService {
}
private void initEvent(ClientSessionModel clientSession) {
UserSessionModel userSession = clientSession.getUserSession();
String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
if (responseType == null) {
responseType = "code";
}
String respMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));
event.event(EventType.LOGIN).client(clientSession.getClient())
.user(clientSession.getUserSession().getUser())
.session(clientSession.getUserSession().getId())
.user(userSession.getUser())
.session(userSession.getId())
.detail(Details.CODE_ID, clientSession.getId())
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
.detail(Details.USERNAME, clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME))
.detail(Details.RESPONSE_TYPE, "code");
UserSessionModel userSession = clientSession.getUserSession();
if (userSession != null) {
event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
event.detail(Details.USERNAME, userSession.getLoginUsername());
.detail(Details.AUTH_METHOD, userSession.getAuthMethod())
.detail(Details.USERNAME, userSession.getLoginUsername())
.detail(Details.RESPONSE_TYPE, responseType)
.detail(Details.RESPONSE_MODE, responseMode.toString().toLowerCase());
if (userSession.isRememberMe()) {
event.detail(Details.REMEMBER_ME, "true");
}
}
}
@Path(REQUIRED_ACTION)
@POST
@ -827,9 +824,14 @@ public class LoginActionsService {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo());
event.detail(Details.CUSTOM_REQUIRED_ACTION, action).error(Errors.REJECTED_BY_USER);
return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
.setUriInfo(context.getUriInfo())
.setEventBuilder(event);
event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
return response;
}
throw new RuntimeException("Unreachable");

View file

@ -8,6 +8,7 @@ import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import org.keycloak.Config;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.events.admin.AdminEvent;
@ -134,7 +135,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
return expect(EventType.CLIENT_LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH)
.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
.removeDetail(Details.CODE_ID)
.session(isUUID());
}

View file

@ -218,7 +218,7 @@ public class CustomFlowTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)

View file

@ -7,6 +7,7 @@ import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.RealmResource;
@ -256,7 +257,7 @@ public class GroupTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)

View file

@ -30,6 +30,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
@ -164,8 +165,8 @@ public class AuthorizationCodeTest {
UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token");
driver.navigate().to(b.build().toURL());
assertEquals("Invalid parameter: response_type", errorPage.getError());
events.expectLogin().error(Errors.INVALID_REQUEST).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent();
assertEquals("Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.", errorPage.getError());
events.expectLogin().error(Errors.NOT_ALLOWED).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, OIDCResponseType.TOKEN).assertEvent();
}
private void assertCode(String expectedCodeId, String actualCode) {

View file

@ -189,7 +189,7 @@ public class ClientAuthSignedJWTTest {
events.expectLogin()
.client("client2")
.session(accessToken.getSessionState())
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, "test-user@localhost")

View file

@ -40,6 +40,7 @@ import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import java.io.IOException;
import java.net.URL;
/**
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
@ -174,7 +175,10 @@ public class OAuthRedirectUriTest {
OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?code="));
URL url = new URL(driver.getCurrentUrl());
Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
Assert.assertTrue(url.getQuery().contains("code="));
Assert.assertTrue(url.getQuery().contains("state="));
}
@Test
@ -192,7 +196,11 @@ public class OAuthRedirectUriTest {
OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code="));
URL url = new URL(driver.getCurrentUrl());
Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
Assert.assertTrue(url.getQuery().contains("key=value"));
Assert.assertTrue(url.getQuery().contains("state="));
Assert.assertTrue(url.getQuery().contains("code="));
}
@Test

View file

@ -319,7 +319,7 @@ public class OfflineTokenTest {
.client("offline-client")
.user(userId)
.session(token.getSessionState())
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
@ -361,7 +361,7 @@ public class OfflineTokenTest {
.client("offline-client")
.user(userId)
.session(token.getSessionState())
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)

View file

@ -6,6 +6,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@ -93,7 +94,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
@ -129,7 +130,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
events.expectLogin()
.client("resource-owner")
.session(accessToken.getSessionState())
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.removeDetail(Details.CODE_ID)
@ -285,7 +286,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
events.expectLogin()
.client("resource-owner")
.session((String) null)
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
.removeDetail(Details.CONSENT)
@ -307,7 +308,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
.client("resource-owner")
.user((String) null)
.session((String) null)
.detail(Details.RESPONSE_TYPE, "token")
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.USERNAME, "invalid")
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)