2019-05-01 15:22:24 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2019 Red Hat, Inc. and/or its affiliates
|
|
|
|
* and other contributors as indicated by the @author tags.
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
package org.keycloak.models;
|
|
|
|
|
2021-06-27 23:57:51 +00:00
|
|
|
import org.keycloak.common.util.Time;
|
|
|
|
|
2019-05-01 15:22:24 +00:00
|
|
|
import javax.ws.rs.core.MultivaluedHashMap;
|
|
|
|
import javax.ws.rs.core.MultivaluedMap;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
|
|
|
*/
|
|
|
|
public class OAuth2DeviceCodeModel {
|
|
|
|
|
2021-01-20 06:36:23 +00:00
|
|
|
private static final String REALM_ID = "rid";
|
2019-05-01 15:22:24 +00:00
|
|
|
private static final String CLIENT_ID = "cid";
|
|
|
|
private static final String EXPIRATION_NOTE = "exp";
|
|
|
|
private static final String POLLING_INTERVAL_NOTE = "int";
|
|
|
|
private static final String NONCE_NOTE = "nonce";
|
|
|
|
private static final String SCOPE_NOTE = "scope";
|
2021-07-22 13:42:42 +00:00
|
|
|
private static final String CLIENT_NOTIFICATION_TOKEN_NOTE = "cnt";
|
|
|
|
private static final String AUTH_REQ_ID_NOTE = "ari";
|
2019-05-01 15:22:24 +00:00
|
|
|
private static final String USER_SESSION_ID_NOTE = "uid";
|
|
|
|
private static final String DENIED_NOTE = "denied";
|
2020-05-19 22:04:24 +00:00
|
|
|
private static final String ADDITIONAL_PARAM_PREFIX = "additional_param_";
|
2022-01-21 07:50:29 +00:00
|
|
|
private static final String CODE_CHALLENGE = "codeChallenge";
|
|
|
|
private static final String CODE_CHALLENGE_METHOD = "codeChallengeMethod";
|
2019-05-01 15:22:24 +00:00
|
|
|
|
|
|
|
private final RealmModel realm;
|
|
|
|
private final String clientId;
|
|
|
|
private final String deviceCode;
|
|
|
|
private final int expiration;
|
|
|
|
private final int pollingInterval;
|
2021-07-22 13:42:42 +00:00
|
|
|
private final String clientNotificationToken;
|
|
|
|
private final String authReqId;
|
2019-05-01 15:22:24 +00:00
|
|
|
private final String scope;
|
|
|
|
private final String nonce;
|
|
|
|
private final String userSessionId;
|
2021-01-20 06:36:23 +00:00
|
|
|
private final Boolean denied;
|
2020-05-19 22:04:24 +00:00
|
|
|
private final Map<String, String> additionalParams;
|
2022-01-21 07:50:29 +00:00
|
|
|
private final String codeChallenge;
|
|
|
|
private final String codeChallengeMethod;
|
2019-05-01 15:22:24 +00:00
|
|
|
|
|
|
|
public static OAuth2DeviceCodeModel create(RealmModel realm, ClientModel client,
|
2021-07-22 13:42:42 +00:00
|
|
|
String deviceCode, String scope, String nonce, int expiresIn, int pollingInterval,
|
2022-01-21 07:50:29 +00:00
|
|
|
String clientNotificationToken, String authReqId, Map<String, String> additionalParams, String codeChallenge, String codeChallengeMethod) {
|
2021-01-20 06:36:23 +00:00
|
|
|
|
2019-05-01 15:22:24 +00:00
|
|
|
int expiration = Time.currentTime() + expiresIn;
|
2022-01-21 07:50:29 +00:00
|
|
|
return new OAuth2DeviceCodeModel(realm, client.getClientId(), deviceCode, scope, nonce, expiration, pollingInterval, clientNotificationToken, authReqId, null, null, additionalParams, codeChallenge, codeChallengeMethod);
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public OAuth2DeviceCodeModel approve(String userSessionId) {
|
2022-01-21 07:50:29 +00:00
|
|
|
return new OAuth2DeviceCodeModel(realm, clientId, deviceCode, scope, nonce, expiration, pollingInterval, clientNotificationToken, authReqId, userSessionId, false, additionalParams, codeChallenge, codeChallengeMethod);
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
2021-06-27 23:57:51 +00:00
|
|
|
public OAuth2DeviceCodeModel approve(String userSessionId, Map<String, String> additionalParams) {
|
|
|
|
if (additionalParams != null) {
|
|
|
|
this.additionalParams.putAll(additionalParams);
|
|
|
|
}
|
2022-01-21 07:50:29 +00:00
|
|
|
return new OAuth2DeviceCodeModel(realm, clientId, deviceCode, scope, nonce, expiration, pollingInterval, clientNotificationToken, authReqId, userSessionId, false, this.additionalParams, codeChallenge, codeChallengeMethod);
|
2021-06-27 23:57:51 +00:00
|
|
|
}
|
|
|
|
|
2019-05-01 15:22:24 +00:00
|
|
|
public OAuth2DeviceCodeModel deny() {
|
2022-01-21 07:50:29 +00:00
|
|
|
return new OAuth2DeviceCodeModel(realm, clientId, deviceCode, scope, nonce, expiration, pollingInterval, clientNotificationToken, authReqId, null, true, additionalParams, codeChallenge, codeChallengeMethod);
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private OAuth2DeviceCodeModel(RealmModel realm, String clientId,
|
2021-07-22 13:42:42 +00:00
|
|
|
String deviceCode, String scope, String nonce, int expiration, int pollingInterval, String clientNotificationToken,
|
2022-01-21 07:50:29 +00:00
|
|
|
String authReqId, String userSessionId, Boolean denied, Map<String, String> additionalParams, String codeChallenge, String codeChallengeMethod) {
|
2019-05-01 15:22:24 +00:00
|
|
|
this.realm = realm;
|
|
|
|
this.clientId = clientId;
|
|
|
|
this.deviceCode = deviceCode;
|
|
|
|
this.scope = scope;
|
|
|
|
this.nonce = nonce;
|
|
|
|
this.expiration = expiration;
|
|
|
|
this.pollingInterval = pollingInterval;
|
2021-07-22 13:42:42 +00:00
|
|
|
this.clientNotificationToken = clientNotificationToken;
|
|
|
|
this.authReqId = authReqId;
|
2019-05-01 15:22:24 +00:00
|
|
|
this.userSessionId = userSessionId;
|
|
|
|
this.denied = denied;
|
2020-05-19 22:04:24 +00:00
|
|
|
this.additionalParams = additionalParams;
|
2022-01-21 07:50:29 +00:00
|
|
|
this.codeChallenge = codeChallenge;
|
|
|
|
this.codeChallengeMethod = codeChallengeMethod;
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public static OAuth2DeviceCodeModel fromCache(RealmModel realm, String deviceCode, Map<String, String> data) {
|
2021-01-20 06:36:23 +00:00
|
|
|
OAuth2DeviceCodeModel model = new OAuth2DeviceCodeModel(realm, deviceCode, data);
|
|
|
|
|
|
|
|
if (!realm.getId().equals(data.get(REALM_ID))) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return model;
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private OAuth2DeviceCodeModel(RealmModel realm, String deviceCode, Map<String, String> data) {
|
2020-05-19 22:04:24 +00:00
|
|
|
this(realm, data.get(CLIENT_ID), deviceCode, data.get(SCOPE_NOTE), data.get(NONCE_NOTE),
|
2021-07-22 13:42:42 +00:00
|
|
|
Integer.parseInt(data.get(EXPIRATION_NOTE)), Integer.parseInt(data.get(POLLING_INTERVAL_NOTE)), data.get(CLIENT_NOTIFICATION_TOKEN_NOTE),
|
2022-01-21 07:50:29 +00:00
|
|
|
data.get(AUTH_REQ_ID_NOTE), data.get(USER_SESSION_ID_NOTE), Boolean.parseBoolean(data.get(DENIED_NOTE)), extractAdditionalParams(data), data.get(CODE_CHALLENGE), data.get(CODE_CHALLENGE_METHOD));
|
2020-05-19 22:04:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Map<String, String> extractAdditionalParams(Map<String, String> data) {
|
|
|
|
Map<String, String> additionalParams = new HashMap<>();
|
|
|
|
for (Map.Entry<String, String> entry : data.entrySet()) {
|
|
|
|
if (entry.getKey().startsWith(ADDITIONAL_PARAM_PREFIX)) {
|
|
|
|
additionalParams.put(entry.getKey().substring(ADDITIONAL_PARAM_PREFIX.length()), entry.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return additionalParams;
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public String getDeviceCode() {
|
|
|
|
return deviceCode;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getScope() {
|
|
|
|
return scope;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getNonce() {
|
|
|
|
return nonce;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getExpiration() {
|
|
|
|
return expiration;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getPollingInterval() {
|
|
|
|
return pollingInterval;
|
|
|
|
}
|
|
|
|
|
2021-07-22 13:42:42 +00:00
|
|
|
public String getClientNotificationToken() {
|
|
|
|
return clientNotificationToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getAuthReqId() {
|
|
|
|
return authReqId;
|
|
|
|
}
|
|
|
|
|
2019-05-01 15:22:24 +00:00
|
|
|
public String getClientId() {
|
|
|
|
return clientId;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isPending() {
|
|
|
|
return userSessionId == null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isDenied() {
|
2021-01-20 06:36:23 +00:00
|
|
|
return denied;
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public String getUserSessionId() {
|
|
|
|
return userSessionId;
|
|
|
|
}
|
|
|
|
|
2021-01-20 06:36:23 +00:00
|
|
|
public static String createKey(String deviceCode) {
|
|
|
|
return String.format("dc.%s", deviceCode);
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public String serializeKey() {
|
2021-01-20 06:36:23 +00:00
|
|
|
return createKey(deviceCode);
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public String serializePollingKey() {
|
2021-01-20 06:36:23 +00:00
|
|
|
return createKey(deviceCode) + ".polling";
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|
|
|
|
|
2022-01-21 07:50:29 +00:00
|
|
|
public String getCodeChallenge() {
|
|
|
|
return codeChallenge;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getCodeChallengeMethod() {
|
|
|
|
return codeChallengeMethod;
|
|
|
|
}
|
|
|
|
|
2021-01-20 06:36:23 +00:00
|
|
|
public Map<String, String> toMap() {
|
2019-05-01 15:22:24 +00:00
|
|
|
Map<String, String> result = new HashMap<>();
|
|
|
|
|
2021-01-20 06:36:23 +00:00
|
|
|
result.put(REALM_ID, realm.getId());
|
2021-07-22 13:42:42 +00:00
|
|
|
if (clientNotificationToken != null) {
|
|
|
|
result.put(CLIENT_NOTIFICATION_TOKEN_NOTE, clientNotificationToken);
|
|
|
|
}
|
|
|
|
if (authReqId != null) {
|
|
|
|
result.put(AUTH_REQ_ID_NOTE, authReqId);
|
|
|
|
}
|
2021-01-20 06:36:23 +00:00
|
|
|
|
|
|
|
if (denied == null) {
|
|
|
|
result.put(CLIENT_ID, clientId);
|
|
|
|
result.put(EXPIRATION_NOTE, String.valueOf(expiration));
|
|
|
|
result.put(POLLING_INTERVAL_NOTE, String.valueOf(pollingInterval));
|
|
|
|
result.put(SCOPE_NOTE, scope);
|
|
|
|
result.put(NONCE_NOTE, nonce);
|
|
|
|
} else if (denied) {
|
|
|
|
result.put(EXPIRATION_NOTE, String.valueOf(expiration));
|
|
|
|
result.put(POLLING_INTERVAL_NOTE, String.valueOf(pollingInterval));
|
|
|
|
result.put(DENIED_NOTE, String.valueOf(denied));
|
|
|
|
} else {
|
|
|
|
result.put(EXPIRATION_NOTE, String.valueOf(expiration));
|
|
|
|
result.put(POLLING_INTERVAL_NOTE, String.valueOf(pollingInterval));
|
|
|
|
result.put(SCOPE_NOTE, scope);
|
|
|
|
result.put(NONCE_NOTE, nonce);
|
|
|
|
result.put(USER_SESSION_ID_NOTE, userSessionId);
|
|
|
|
}
|
2022-01-21 07:50:29 +00:00
|
|
|
if (codeChallenge != null)
|
|
|
|
result.put(CODE_CHALLENGE, codeChallenge);
|
|
|
|
if (codeChallengeMethod != null)
|
|
|
|
result.put(CODE_CHALLENGE_METHOD, codeChallengeMethod);
|
2019-05-01 15:22:24 +00:00
|
|
|
|
2020-05-19 22:04:24 +00:00
|
|
|
additionalParams.forEach((key, value) -> result.put(ADDITIONAL_PARAM_PREFIX + key, value));
|
2021-01-20 06:36:23 +00:00
|
|
|
|
2019-05-01 15:22:24 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public MultivaluedMap<String, String> getParams() {
|
|
|
|
MultivaluedHashMap<String, String> params = new MultivaluedHashMap<>();
|
|
|
|
params.putSingle(SCOPE_NOTE, scope);
|
2020-05-19 22:04:24 +00:00
|
|
|
if (nonce != null) {
|
|
|
|
params.putSingle(NONCE_NOTE, nonce);
|
|
|
|
}
|
|
|
|
this.additionalParams.forEach(params::putSingle);
|
2019-05-01 15:22:24 +00:00
|
|
|
return params;
|
|
|
|
}
|
2021-01-20 06:36:23 +00:00
|
|
|
|
2021-06-27 23:57:51 +00:00
|
|
|
public Map<String, String> getAdditionalParams() {
|
|
|
|
return additionalParams;
|
|
|
|
}
|
|
|
|
|
2021-01-20 06:36:23 +00:00
|
|
|
public boolean isExpired() {
|
|
|
|
return getExpiration() - Time.currentTime() < 0;
|
|
|
|
}
|
2019-05-01 15:22:24 +00:00
|
|
|
}
|