KEYCLOAK-7675 SPI and default implementation for Device User Code.
Author: Hiroyuki Wada <h2-wada@nri.co.jp> Date: Sun May 12 15:47:15 2019 +0900 Signed-off-by: Łukasz Dywicki <luke@code-house.org>
This commit is contained in:
parent
9d57b88dba
commit
5edf14944e
8 changed files with 263 additions and 9 deletions
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.common.util.RandomString;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* The default implementation for generating/formatting user code of OAuth 2.0 Device Authorization Grant.
|
||||
* For generation, uppercase eight-letter format is used.
|
||||
* For display, uppercase four-letters dashes four-letters format is used.
|
||||
*
|
||||
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
||||
*/
|
||||
public class DefaultOAuth2DeviceUserCodeProvider implements OAuth2DeviceUserCodeProvider {
|
||||
|
||||
private static final int LENGTH = 8;
|
||||
private static final String DELIMITER = "-";
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
// For case-insensitive, use uppercase
|
||||
return new RandomString(LENGTH, new SecureRandom(), RandomString.upper).nextString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String display(String userCode) {
|
||||
return new StringBuilder(userCode).insert(4, DELIMITER).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(String userCode) {
|
||||
return String.join("", userCode.split(DELIMITER)).toUpperCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.Config;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
||||
*/
|
||||
public class DefaultOAuth2DeviceUserCodeProviderFactory implements OAuth2DeviceUserCodeProviderFactory {
|
||||
|
||||
@Override
|
||||
public OAuth2DeviceUserCodeProvider create(KeycloakSession session) {
|
||||
return new DefaultOAuth2DeviceUserCodeProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
||||
*/
|
||||
public interface OAuth2DeviceUserCodeProvider extends Provider {
|
||||
|
||||
/**
|
||||
* Generate a new user code for OAuth 2.0 Device Authorization Grant.
|
||||
*
|
||||
* @return Return a generated user code
|
||||
*/
|
||||
String generate();
|
||||
|
||||
/**
|
||||
* Get human-readability user code from original user code.
|
||||
*
|
||||
* @param userCode Original user code
|
||||
* @return Return a human-readability user code
|
||||
*/
|
||||
String display(String userCode);
|
||||
|
||||
/**
|
||||
* Format inputted user code.
|
||||
*
|
||||
* @param userCode Inputted user code.
|
||||
* @return
|
||||
*/
|
||||
String format(String userCode);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
||||
*/
|
||||
public interface OAuth2DeviceUserCodeProviderFactory extends ProviderFactory<OAuth2DeviceUserCodeProvider> {
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
||||
*/
|
||||
public class OAuth2DeviceUserCodeSpi implements Spi {
|
||||
|
||||
public static final String NAME = "oauth2DeviceUserCode";
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass(){
|
||||
return OAuth2DeviceUserCodeProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return OAuth2DeviceUserCodeProviderFactory.class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
org.keycloak.models.DefaultOAuth2DeviceUserCodeProviderFactory
|
|
@ -26,6 +26,7 @@ org.keycloak.models.RoleSpi
|
|||
org.keycloak.models.ActionTokenStoreSpi
|
||||
org.keycloak.models.CodeToTokenStoreSpi
|
||||
org.keycloak.models.OAuth2DeviceTokenStoreSpi
|
||||
org.keycloak.models.OAuth2DeviceUserCodeSpi
|
||||
org.keycloak.models.SingleUseTokenStoreSpi
|
||||
org.keycloak.models.TokenRevocationStoreSpi
|
||||
org.keycloak.models.UserSessionSpi
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.authentication.AuthenticationProcessor;
|
||||
import org.keycloak.common.util.Base64Url;
|
||||
import org.keycloak.common.util.RandomString;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.events.Details;
|
||||
|
@ -35,6 +34,7 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.OAuth2DeviceCodeModel;
|
||||
import org.keycloak.models.OAuth2DeviceTokenStoreProvider;
|
||||
import org.keycloak.models.OAuth2DeviceUserCodeModel;
|
||||
import org.keycloak.models.OAuth2DeviceUserCodeProvider;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.SystemClientUtil;
|
||||
|
@ -50,7 +50,6 @@ import org.keycloak.services.ErrorPageException;
|
|||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
|
@ -58,7 +57,6 @@ import org.keycloak.services.resources.LoginActionsService;
|
|||
import org.keycloak.services.resources.RealmsResource;
|
||||
import org.keycloak.services.util.CacheControlUtil;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
|
@ -67,7 +65,6 @@ import javax.ws.rs.POST;
|
|||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint.LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX;
|
||||
|
||||
|
@ -138,8 +135,8 @@ public class OAuth2DeviceAuthorizationEndpoint extends AuthorizationEndpointBase
|
|||
OAuth2DeviceCodeModel deviceCode = OAuth2DeviceCodeModel.create(realm, client,
|
||||
Base64Url.encode(KeycloakModelUtils.generateSecret()), request.getScope(), request.getNonce());
|
||||
|
||||
// TODO Configure user code format
|
||||
String secret = new RandomString(10, new SecureRandom(), RandomString.upper).nextString();
|
||||
OAuth2DeviceUserCodeProvider userCodeProvider = session.getProvider(OAuth2DeviceUserCodeProvider.class);
|
||||
String secret = userCodeProvider.generate();
|
||||
OAuth2DeviceUserCodeModel userCode = new OAuth2DeviceUserCodeModel(realm,
|
||||
deviceCode.getDeviceCode(),
|
||||
secret);
|
||||
|
@ -155,7 +152,7 @@ public class OAuth2DeviceAuthorizationEndpoint extends AuthorizationEndpointBase
|
|||
|
||||
OAuth2DeviceAuthorizationResponse response = new OAuth2DeviceAuthorizationResponse();
|
||||
response.setDeviceCode(deviceCode.getDeviceCode());
|
||||
response.setUserCode(secret);
|
||||
response.setUserCode(userCodeProvider.display(secret));
|
||||
response.setExpiresIn(expiresIn);
|
||||
response.setInterval(interval);
|
||||
response.setVerificationUri(deviceUrl);
|
||||
|
@ -272,8 +269,13 @@ public class OAuth2DeviceAuthorizationEndpoint extends AuthorizationEndpointBase
|
|||
return createVerificationPage(Messages.OAUTH2_DEVICE_INVALID_USER_CODE);
|
||||
}
|
||||
|
||||
// Format inputted user code
|
||||
OAuth2DeviceUserCodeProvider userCodeProvider = session.getProvider(OAuth2DeviceUserCodeProvider.class);
|
||||
String formattedUserCode = userCodeProvider.format(userCode);
|
||||
|
||||
// Find the token from store
|
||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
||||
OAuth2DeviceCodeModel deviceCodeModel = store.getByUserCode(realm, userCode);
|
||||
OAuth2DeviceCodeModel deviceCodeModel = store.getByUserCode(realm, formattedUserCode);
|
||||
if (deviceCodeModel == null) {
|
||||
event.error(Errors.INVALID_OAUTH2_USER_CODE);
|
||||
return createVerificationPage(Messages.OAUTH2_DEVICE_INVALID_USER_CODE);
|
||||
|
@ -289,7 +291,7 @@ public class OAuth2DeviceAuthorizationEndpoint extends AuthorizationEndpointBase
|
|||
updateAuthenticationSession(deviceCodeModel);
|
||||
|
||||
// Verification OK
|
||||
authenticationSession.setClientNote(OIDCLoginProtocol.OAUTH2_DEVICE_VERIFIED_USER_CODE, userCode);
|
||||
authenticationSession.setClientNote(OIDCLoginProtocol.OAUTH2_DEVICE_VERIFIED_USER_CODE, formattedUserCode);
|
||||
|
||||
// Event logging for the verification
|
||||
event.client(deviceCodeModel.getClientId())
|
||||
|
|
Loading…
Reference in a new issue