/* * Copyright 2016 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.client.admin.cli.util; import org.keycloak.client.admin.cli.config.ConfigData; import org.keycloak.client.admin.cli.config.RealmConfigData; import org.keycloak.common.util.KeystoreUtil; import org.keycloak.common.util.Time; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.JsonWebToken; import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.KeyPair; import java.util.UUID; import static java.lang.System.currentTimeMillis; import static org.keycloak.client.admin.cli.util.ConfigUtil.checkAuthInfo; import static org.keycloak.client.admin.cli.util.ConfigUtil.saveMergeConfig; import static org.keycloak.client.admin.cli.util.HttpUtil.APPLICATION_FORM_URL_ENCODED; import static org.keycloak.client.admin.cli.util.HttpUtil.APPLICATION_JSON; import static org.keycloak.client.admin.cli.util.HttpUtil.doPost; import static org.keycloak.client.admin.cli.util.HttpUtil.urlencode; /** * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a> */ public class AuthUtil { public static String ensureToken(ConfigData config) { checkAuthInfo(config); RealmConfigData realmConfig = config.sessionRealmConfigData(); long now = currentTimeMillis(); // check expires of access_token against time // if it's less than 5s to expiry, renew it if (realmConfig.getExpiresAt() - now < 5000) { // check refresh_token against expiry time // if it's less than 5s to expiry, fail with credentials expired if (realmConfig.getRefreshExpiresAt() - now < 5000) { throw new RuntimeException("Session has expired. Login again with '" + OsUtil.CMD + " config credentials'"); } if (realmConfig.getSigExpiresAt() != null && realmConfig.getSigExpiresAt() - now < 5000) { throw new RuntimeException("Session has expired. Login again with '" + OsUtil.CMD + " config credentials'"); } try { String authorization = null; StringBuilder body = new StringBuilder("grant_type=refresh_token") .append("&refresh_token=").append(realmConfig.getRefreshToken()) .append("&client_id=").append(urlencode(realmConfig.getClientId())); if (realmConfig.getSigningToken() != null) { body.append("&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer") .append("&client_assertion=").append(realmConfig.getSigningToken()); } else if (realmConfig.getSecret() != null) { authorization = BasicAuthHelper.createHeader(realmConfig.getClientId(), realmConfig.getSecret()); } InputStream result = doPost(realmConfig.serverUrl() + "/realms/" + realmConfig.realm() + "/protocol/openid-connect/token", APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), authorization); AccessTokenResponse token = JsonSerialization.readValue(result, AccessTokenResponse.class); saveMergeConfig(cfg -> { RealmConfigData realmData = cfg.sessionRealmConfigData(); realmData.setToken(token.getToken()); realmData.setRefreshToken(token.getRefreshToken()); realmData.setExpiresAt(currentTimeMillis() + token.getExpiresIn() * 1000); realmData.setRefreshExpiresAt(currentTimeMillis() + token.getRefreshExpiresIn() * 1000); }); return token.getToken(); } catch (Exception e) { throw new RuntimeException("Failed to refresh access token - " + e.getMessage(), e); } } return realmConfig.getToken(); } public static AccessTokenResponse getAuthTokens(String server, String realm, String user, String password, String clientId) { StringBuilder body = new StringBuilder(); try { body.append("grant_type=password") .append("&username=").append(urlencode(user)) .append("&password=").append(urlencode(password)) .append("&client_id=").append(urlencode(clientId)); InputStream result = doPost(server + "/realms/" + realm + "/protocol/openid-connect/token", APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), null); return JsonSerialization.readValue(result, AccessTokenResponse.class); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unexpected error: ", e); } catch (IOException e) { throw new RuntimeException("Error receiving response: ", e); } } public static AccessTokenResponse getAuthTokensByJWT(String server, String realm, String user, String password, String clientId, String signedRequestToken) { StringBuilder body = new StringBuilder(); try { body.append("client_id=").append(urlencode(clientId)) .append("&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer") .append("&client_assertion=").append(signedRequestToken); if (user != null) { if (password == null) { throw new RuntimeException("No password specified"); } body.append("&grant_type=password") .append("&username=").append(urlencode(user)) .append("&password=").append(urlencode(password)); } else { body.append("&grant_type=client_credentials"); } InputStream result = doPost(server + "/realms/" + realm + "/protocol/openid-connect/token", APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), null); return JsonSerialization.readValue(result, AccessTokenResponse.class); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unexpected error: ", e); } catch (IOException e) { throw new RuntimeException("Error receiving response: ", e); } } public static AccessTokenResponse getAuthTokensBySecret(String server, String realm, String user, String password, String clientId, String secret) { StringBuilder body = new StringBuilder(); try { if (user != null) { if (password == null) { throw new RuntimeException("No password specified"); } body.append("client_id=").append(urlencode(clientId)) .append("&grant_type=password") .append("&username=").append(urlencode(user)) .append("&password=").append(urlencode(password)); } else { body.append("grant_type=client_credentials"); } InputStream result = doPost(server + "/realms/" + realm + "/protocol/openid-connect/token", APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), BasicAuthHelper.createHeader(clientId, secret)); return JsonSerialization.readValue(result, AccessTokenResponse.class); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unexpected error: ", e); } catch (IOException e) { throw new RuntimeException("Error receiving response: ", e); } } public static String getSignedRequestToken(String keystore, String storePass, String keyPass, String alias, int sigLifetime, String clientId, String realmInfoUrl) { KeyPair keypair = KeystoreUtil.loadKeyPairFromKeystore(keystore, storePass, keyPass, alias, KeystoreUtil.KeystoreFormat.JKS); JsonWebToken reqToken = new JsonWebToken(); reqToken.id(UUID.randomUUID().toString()); reqToken.issuer(clientId); reqToken.subject(clientId); reqToken.audience(realmInfoUrl); int now = Time.currentTime(); reqToken.issuedAt(now); reqToken.expiration(now + sigLifetime); reqToken.notBefore(now); String signedRequestToken = new JWSBuilder() .jsonContent(reqToken) .rsa256(keypair.getPrivate()); return signedRequestToken; } }