Search in sources :

Example 1 with OAuth2DeviceCodeModel

use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.

the class DeviceGrantType method oauth2DeviceFlow.

public Response oauth2DeviceFlow() {
    if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
        event.error(Errors.NOT_ALLOWED);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Client not allowed OAuth 2.0 Device Authorization Grant", Response.Status.BAD_REQUEST);
    }
    String deviceCode = formParams.getFirst(OAuth2Constants.DEVICE_CODE);
    if (deviceCode == null) {
        event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.DEVICE_CODE, Response.Status.BAD_REQUEST);
    }
    OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
    OAuth2DeviceCodeModel deviceCodeModel = store.getByDeviceCode(realm, deviceCode);
    if (deviceCodeModel == null) {
        event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Device code not valid", Response.Status.BAD_REQUEST);
    }
    if (deviceCodeModel.isExpired()) {
        event.error(Errors.EXPIRED_OAUTH2_DEVICE_CODE);
        throw new CorsErrorResponseException(cors, OAuthErrorException.EXPIRED_TOKEN, "Device code is expired", Response.Status.BAD_REQUEST);
    }
    if (!store.isPollingAllowed(deviceCodeModel)) {
        event.error(Errors.SLOW_DOWN);
        throw new CorsErrorResponseException(cors, OAuthErrorException.SLOW_DOWN, "Slow down", Response.Status.BAD_REQUEST);
    }
    if (deviceCodeModel.isDenied()) {
        event.error(Errors.ACCESS_DENIED);
        throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "The end user denied the authorization request", Response.Status.BAD_REQUEST);
    }
    if (deviceCodeModel.isPending()) {
        throw new CorsErrorResponseException(cors, OAuthErrorException.AUTHORIZATION_PENDING, "The authorization request is still pending", Response.Status.BAD_REQUEST);
    }
    // https://tools.ietf.org/html/rfc7636#section-4.6
    String codeVerifier = formParams.getFirst(OAuth2Constants.CODE_VERIFIER);
    String codeChallenge = deviceCodeModel.getCodeChallenge();
    String codeChallengeMethod = deviceCodeModel.getCodeChallengeMethod();
    if (codeChallengeMethod != null && !codeChallengeMethod.isEmpty()) {
        PkceUtils.checkParamsForPkceEnforcedClient(codeVerifier, codeChallenge, codeChallengeMethod, null, null, event, cors);
    } else {
        // PKCE Activation is OFF, execute the codes implemented in KEYCLOAK-2604
        PkceUtils.checkParamsForPkceNotEnforcedClient(codeVerifier, codeChallenge, codeChallengeMethod, null, null, event, cors);
    }
    // Approved
    String userSessionId = deviceCodeModel.getUserSessionId();
    event.detail(Details.CODE_ID, userSessionId);
    event.session(userSessionId);
    // Retrieve UserSession
    UserSessionModel userSession = new UserSessionCrossDCManager(session).getUserSessionWithClient(realm, userSessionId, client.getId());
    if (userSession == null) {
        userSession = session.sessions().getUserSession(realm, userSessionId);
        if (userSession == null) {
            throw new CorsErrorResponseException(cors, OAuthErrorException.AUTHORIZATION_PENDING, "The authorization request is verified but can not lookup the user session yet", Response.Status.BAD_REQUEST);
        }
    }
    // Now, remove the device code
    store.removeDeviceCode(realm, deviceCode);
    UserModel user = userSession.getUser();
    if (user == null) {
        event.error(Errors.USER_NOT_FOUND);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "User not found", Response.Status.BAD_REQUEST);
    }
    event.user(userSession.getUser());
    if (!user.isEnabled()) {
        event.error(Errors.USER_DISABLED);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "User disabled", Response.Status.BAD_REQUEST);
    }
    AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
    if (!client.getClientId().equals(clientSession.getClient().getClientId())) {
        event.error(Errors.INVALID_CODE);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Auth error", Response.Status.BAD_REQUEST);
    }
    if (!AuthenticationManager.isSessionValid(realm, userSession)) {
        event.error(Errors.USER_SESSION_NOT_FOUND);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Session not active", Response.Status.BAD_REQUEST);
    }
    try {
        session.clientPolicy().triggerOnEvent(new DeviceTokenRequestContext(deviceCodeModel, formParams));
    } catch (ClientPolicyException cpe) {
        event.error(cpe.getError());
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
    }
    // Compute client scopes again from scope parameter. Check if user still has them granted
    // (but in device_code-to-token request, it could just theoretically happen that they are not available)
    String scopeParam = deviceCodeModel.getScope();
    if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
        event.error(Errors.NOT_ALLOWED);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
    }
    ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(clientSession, scopeParam, session);
    // Set nonce as an attribute in the ClientSessionContext. Will be used for the token generation
    clientSessionCtx.setAttribute(OIDCLoginProtocol.NONCE_PARAM, deviceCodeModel.getNonce());
    return tokenEndpoint.createTokenResponse(user, userSession, clientSessionCtx, scopeParam, false);
}
Also used : OAuth2DeviceTokenStoreProvider(org.keycloak.models.OAuth2DeviceTokenStoreProvider) UserModel(org.keycloak.models.UserModel) UserSessionModel(org.keycloak.models.UserSessionModel) DeviceTokenRequestContext(org.keycloak.protocol.oidc.grants.device.clientpolicy.context.DeviceTokenRequestContext) DefaultClientSessionContext(org.keycloak.services.util.DefaultClientSessionContext) ClientSessionContext(org.keycloak.models.ClientSessionContext) OAuth2DeviceCodeModel(org.keycloak.models.OAuth2DeviceCodeModel) AuthenticatedClientSessionModel(org.keycloak.models.AuthenticatedClientSessionModel) CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException) UserSessionCrossDCManager(org.keycloak.services.managers.UserSessionCrossDCManager) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException)

Example 2 with OAuth2DeviceCodeModel

use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.

the class CibaGrantType method cibaGrant.

public Response cibaGrant() {
    ProfileHelper.requireFeature(Profile.Feature.CIBA);
    if (!realm.getCibaPolicy().isOIDCCIBAGrantEnabled(client)) {
        event.error(Errors.NOT_ALLOWED);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Client not allowed OIDC CIBA Grant", Response.Status.BAD_REQUEST);
    }
    String jwe = formParams.getFirst(AUTH_REQ_ID);
    if (jwe == null) {
        event.error(Errors.INVALID_CODE);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + AUTH_REQ_ID, Response.Status.BAD_REQUEST);
    }
    logger.tracev("CIBA Grant :: authReqId = {0}", jwe);
    CIBAAuthenticationRequest request;
    try {
        request = CIBAAuthenticationRequest.deserialize(session, jwe);
    } catch (Exception e) {
        logger.warnf("illegal format of auth_req_id : e.getMessage() = %s", e.getMessage());
        // Auth Req ID has not put onto cache, no need to remove Auth Req ID.
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid Auth Req ID", Response.Status.BAD_REQUEST);
    }
    request.setClient(client);
    try {
        session.clientPolicy().triggerOnEvent(new BackchannelTokenRequestContext(request, formParams));
    } catch (ClientPolicyException cpe) {
        event.error(cpe.getError());
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
    }
    OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
    OAuth2DeviceCodeModel deviceCode = store.getByDeviceCode(realm, request.getId());
    if (deviceCode == null) {
        // Auth Req ID has not put onto cache, no need to remove Auth Req ID.
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "Invalid " + AUTH_REQ_ID, Response.Status.BAD_REQUEST);
    }
    if (!request.getIssuedFor().equals(client.getClientId())) {
        logDebug("invalid client.", request);
        // the client sending this Auth Req ID does not match the client to which keycloak had issued Auth Req ID.
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, "unauthorized client", Response.Status.BAD_REQUEST);
    }
    if (deviceCode.isExpired()) {
        logDebug("expired.", request);
        throw new CorsErrorResponseException(cors, OAuthErrorException.EXPIRED_TOKEN, "authentication timed out", Response.Status.BAD_REQUEST);
    }
    if (!store.isPollingAllowed(deviceCode)) {
        logDebug("pooling.", request);
        throw new CorsErrorResponseException(cors, OAuthErrorException.SLOW_DOWN, "too early to access", Response.Status.BAD_REQUEST);
    }
    if (deviceCode.isDenied()) {
        logDebug("denied.", request);
        throw new CorsErrorResponseException(cors, OAuthErrorException.ACCESS_DENIED, "not authorized", Response.Status.BAD_REQUEST);
    }
    // get corresponding Authentication Channel Result entry
    if (deviceCode.isPending()) {
        logDebug("not yet authenticated by Authentication Device or auth_req_id has already been used to get tokens.", request);
        throw new CorsErrorResponseException(cors, OAuthErrorException.AUTHORIZATION_PENDING, "The authorization request is still pending as the end-user hasn't yet been authenticated.", Response.Status.BAD_REQUEST);
    }
    UserSessionModel userSession = createUserSession(request, deviceCode.getAdditionalParams());
    UserModel user = userSession.getUser();
    store.removeDeviceCode(realm, request.getId());
    // Compute client scopes again from scope parameter. Check if user still has them granted
    // (but in code-to-token request, it could just theoretically happen that they are not available)
    String scopeParam = request.getScope();
    if (!TokenManager.verifyConsentStillAvailable(session, user, client, TokenManager.getRequestedClientScopes(scopeParam, client))) {
        event.error(Errors.NOT_ALLOWED);
        throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_SCOPE, "Client no longer has requested consent from user", Response.Status.BAD_REQUEST);
    }
    ClientSessionContext clientSessionCtx = DefaultClientSessionContext.fromClientSessionAndScopeParameter(userSession.getAuthenticatedClientSessionByClient(client.getId()), scopeParam, session);
    int authTime = Time.currentTime();
    userSession.setNote(AuthenticationManager.AUTH_TIME, String.valueOf(authTime));
    return tokenEndpoint.createTokenResponse(user, userSession, clientSessionCtx, scopeParam, true);
}
Also used : OAuth2DeviceTokenStoreProvider(org.keycloak.models.OAuth2DeviceTokenStoreProvider) UserModel(org.keycloak.models.UserModel) UserSessionModel(org.keycloak.models.UserSessionModel) DefaultClientSessionContext(org.keycloak.services.util.DefaultClientSessionContext) ClientSessionContext(org.keycloak.models.ClientSessionContext) OAuth2DeviceCodeModel(org.keycloak.models.OAuth2DeviceCodeModel) CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException) BackchannelTokenRequestContext(org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context.BackchannelTokenRequestContext) CIBAAuthenticationRequest(org.keycloak.protocol.oidc.grants.ciba.channel.CIBAAuthenticationRequest) OAuthErrorException(org.keycloak.OAuthErrorException) ErrorResponseException(org.keycloak.services.ErrorResponseException) CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException) CibaRootEndpoint(org.keycloak.protocol.oidc.grants.ciba.endpoints.CibaRootEndpoint) TokenEndpoint(org.keycloak.protocol.oidc.endpoints.TokenEndpoint) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException)

Example 3 with OAuth2DeviceCodeModel

use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.

the class DeviceEndpoint method handleDeviceRequest.

/**
 * Handles device authorization requests.
 *
 * @return the device authorization response.
 */
@Path("")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response handleDeviceRequest() {
    cors = Cors.add(request).auth().allowedMethods("POST").auth().exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS);
    logger.trace("Processing @POST request");
    event.event(EventType.OAUTH2_DEVICE_AUTH);
    checkSsl();
    checkRealm();
    ClientModel client = authenticateClient();
    AuthorizationEndpointRequest request = AuthorizationEndpointRequestParserProcessor.parseRequest(event, session, client, httpRequest.getDecodedFormParameters());
    if (!TokenUtil.isOIDCRequest(request.getScope())) {
        ServicesLogger.LOGGER.oidcScopeMissing();
    }
    // So back button doesn't work
    CacheControlUtil.noBackButtonCacheControlHeader();
    if (!realm.getOAuth2DeviceConfig().isOAuth2DeviceAuthorizationGrantEnabled(client)) {
        event.error(Errors.NOT_ALLOWED);
        throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Client not allowed for OAuth 2.0 Device Authorization Grant", Response.Status.BAD_REQUEST);
    }
    // https://tools.ietf.org/html/rfc7636#section-4
    AuthorizationEndpointChecker checker = new AuthorizationEndpointChecker().event(event).client(client).request(request);
    try {
        checker.checkPKCEParams();
    } catch (AuthorizationEndpointChecker.AuthorizationCheckException ex) {
        throw new ErrorResponseException(ex.getError(), ex.getErrorDescription(), Response.Status.BAD_REQUEST);
    }
    try {
        session.clientPolicy().triggerOnEvent(new DeviceAuthorizationRequestContext(request, httpRequest.getDecodedFormParameters()));
    } catch (ClientPolicyException cpe) {
        throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
    }
    int expiresIn = realm.getOAuth2DeviceConfig().getLifespan(client);
    int interval = realm.getOAuth2DeviceConfig().getPoolingInterval(client);
    OAuth2DeviceCodeModel deviceCode = OAuth2DeviceCodeModel.create(realm, client, Base64Url.encode(SecretGenerator.getInstance().randomBytes()), request.getScope(), request.getNonce(), expiresIn, interval, null, null, request.getAdditionalReqParams(), request.getCodeChallenge(), request.getCodeChallengeMethod());
    OAuth2DeviceUserCodeProvider userCodeProvider = session.getProvider(OAuth2DeviceUserCodeProvider.class);
    String secret = userCodeProvider.generate();
    OAuth2DeviceUserCodeModel userCode = new OAuth2DeviceUserCodeModel(realm, deviceCode.getDeviceCode(), secret);
    // To inform "expired_token" to the client, the lifespan of the cache provider is longer than device code
    int lifespanSeconds = expiresIn + interval + 10;
    OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
    store.put(deviceCode, userCode, lifespanSeconds);
    try {
        String deviceUrl = DeviceGrantType.oauth2DeviceVerificationUrl(session.getContext().getUri()).build(realm.getName()).toString();
        OAuth2DeviceAuthorizationResponse response = new OAuth2DeviceAuthorizationResponse();
        response.setDeviceCode(deviceCode.getDeviceCode());
        response.setUserCode(userCodeProvider.display(secret));
        response.setExpiresIn(expiresIn);
        response.setInterval(interval);
        response.setVerificationUri(deviceUrl);
        response.setVerificationUriComplete(deviceUrl + "?user_code=" + response.getUserCode());
        return cors.builder(Response.ok(JsonSerialization.writeValueAsBytes(response)).type(MediaType.APPLICATION_JSON_TYPE)).build();
    } catch (Exception e) {
        throw new RuntimeException("Error creating OAuth 2.0 Device Authorization Response.", e);
    }
}
Also used : DeviceAuthorizationRequestContext(org.keycloak.protocol.oidc.grants.device.clientpolicy.context.DeviceAuthorizationRequestContext) OAuth2DeviceTokenStoreProvider(org.keycloak.models.OAuth2DeviceTokenStoreProvider) AuthorizationEndpointRequest(org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest) OAuthErrorException(org.keycloak.OAuthErrorException) ErrorResponseException(org.keycloak.services.ErrorResponseException) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException) ErrorPageException(org.keycloak.services.ErrorPageException) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException) OAuth2DeviceUserCodeProvider(org.keycloak.models.OAuth2DeviceUserCodeProvider) ClientModel(org.keycloak.models.ClientModel) AuthorizationEndpointChecker(org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker) OAuth2DeviceCodeModel(org.keycloak.models.OAuth2DeviceCodeModel) OAuth2DeviceAuthorizationResponse(org.keycloak.representations.OAuth2DeviceAuthorizationResponse) ErrorResponseException(org.keycloak.services.ErrorResponseException) OAuth2DeviceUserCodeModel(org.keycloak.models.OAuth2DeviceUserCodeModel) Path(javax.ws.rs.Path) POST(javax.ws.rs.POST) Consumes(javax.ws.rs.Consumes) Produces(javax.ws.rs.Produces)

Example 4 with OAuth2DeviceCodeModel

use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.

the class DeviceEndpoint method verifyUserCode.

/**
 * This endpoint is used by end-users to start the flow to authorize a device.
 *
 * @param userCode the user code to authorize
 * @return
 */
@GET
public Response verifyUserCode(@QueryParam("user_code") String userCode) {
    event.event(EventType.OAUTH2_DEVICE_VERIFY_USER_CODE);
    checkSsl();
    checkRealm();
    // So back button doesn't work
    CacheControlUtil.noBackButtonCacheControlHeader();
    // code is not known, we can infer the client neither. ask the user to provide the code.
    if (StringUtil.isNullOrEmpty(userCode)) {
        return createVerificationPage(null);
    } else {
        // code exists, probably due to using a verification_uri_complete. Start the authentication considering the client
        // that started the flow.
        OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
        OAuth2DeviceUserCodeProvider userCodeProvider = session.getProvider(OAuth2DeviceUserCodeProvider.class);
        String formattedUserCode = userCodeProvider.format(userCode);
        OAuth2DeviceCodeModel deviceCode = store.getByUserCode(realm, formattedUserCode);
        if (deviceCode == null) {
            return invalidUserCodeResponse(Messages.OAUTH2_DEVICE_INVALID_USER_CODE, "device code not found (it may already have been used)");
        }
        if (!deviceCode.isPending()) {
            event.detail("device_code_user_session_id", deviceCode.getUserSessionId());
            return invalidUserCodeResponse(Messages.OAUTH2_DEVICE_INVALID_USER_CODE, "device code already used and not yet deleted");
        }
        if (deviceCode.isDenied()) {
            return invalidUserCodeResponse(Messages.OAUTH2_DEVICE_INVALID_USER_CODE, "device code denied");
        }
        if (deviceCode.isExpired()) {
            return invalidUserCodeResponse(Messages.OAUTH2_DEVICE_EXPIRED_USER_CODE, "device code expired");
        }
        return processVerification(deviceCode, formattedUserCode);
    }
}
Also used : OAuth2DeviceTokenStoreProvider(org.keycloak.models.OAuth2DeviceTokenStoreProvider) OAuth2DeviceCodeModel(org.keycloak.models.OAuth2DeviceCodeModel) OAuth2DeviceUserCodeProvider(org.keycloak.models.OAuth2DeviceUserCodeProvider) GET(javax.ws.rs.GET)

Example 5 with OAuth2DeviceCodeModel

use of org.keycloak.models.OAuth2DeviceCodeModel in project keycloak by keycloak.

the class BackchannelAuthenticationCallbackEndpoint method cancelRequest.

private void cancelRequest(String authResultId) {
    OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
    OAuth2DeviceCodeModel userCode = store.getByUserCode(realm, authResultId);
    store.removeDeviceCode(realm, userCode.getDeviceCode());
    store.removeUserCode(realm, authResultId);
}
Also used : OAuth2DeviceTokenStoreProvider(org.keycloak.models.OAuth2DeviceTokenStoreProvider) OAuth2DeviceCodeModel(org.keycloak.models.OAuth2DeviceCodeModel)

Aggregations

OAuth2DeviceCodeModel (org.keycloak.models.OAuth2DeviceCodeModel)10 OAuth2DeviceTokenStoreProvider (org.keycloak.models.OAuth2DeviceTokenStoreProvider)7 ClientModel (org.keycloak.models.ClientModel)4 ErrorResponseException (org.keycloak.services.ErrorResponseException)4 OAuthErrorException (org.keycloak.OAuthErrorException)3 ClientPolicyException (org.keycloak.services.clientpolicy.ClientPolicyException)3 Consumes (javax.ws.rs.Consumes)2 POST (javax.ws.rs.POST)2 Path (javax.ws.rs.Path)2 Produces (javax.ws.rs.Produces)2 HotRodClientException (org.infinispan.client.hotrod.exceptions.HotRodClientException)2 ClientSessionContext (org.keycloak.models.ClientSessionContext)2 OAuth2DeviceUserCodeModel (org.keycloak.models.OAuth2DeviceUserCodeModel)2 OAuth2DeviceUserCodeProvider (org.keycloak.models.OAuth2DeviceUserCodeProvider)2 UserModel (org.keycloak.models.UserModel)2 UserSessionModel (org.keycloak.models.UserSessionModel)2 ActionTokenValueEntity (org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity)2 AccessToken (org.keycloak.representations.AccessToken)2 CorsErrorResponseException (org.keycloak.services.CorsErrorResponseException)2 DefaultClientSessionContext (org.keycloak.services.util.DefaultClientSessionContext)2