/* * 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.authentication.authenticators.broker.util; import com.fasterxml.jackson.annotation.JsonIgnore; import org.keycloak.authentication.requiredactions.util.UpdateProfileContext; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.IdentityProviderDataMarshaller; import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.reflections.Reflections; import org.keycloak.models.Constants; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.services.resources.IdentityBrokerService; import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public class SerializedBrokeredIdentityContext implements UpdateProfileContext { private String id; private String brokerUsername; private String modelUsername; private String email; private String firstName; private String lastName; private String brokerSessionId; private String brokerUserId; private String code; private String token; @JsonIgnore private boolean emailAsUsername; private String identityProviderId; private Map<String, ContextDataEntry> contextData = new HashMap<>(); @JsonIgnore @Override public boolean isEditUsernameAllowed() { return !emailAsUsername; } public String getId() { return id; } public void setId(String id) { this.id = id; } @JsonIgnore @Override public String getUsername() { return modelUsername; } @Override public void setUsername(String username) { this.modelUsername = username; } public String getModelUsername() { return modelUsername; } public void setModelUsername(String modelUsername) { this.modelUsername = modelUsername; } public String getBrokerUsername() { return brokerUsername; } public void setBrokerUsername(String modelUsername) { this.brokerUsername = modelUsername; } @Override public String getEmail() { return email; } @Override public void setEmail(String email) { this.email = email; } @Override public String getFirstName() { return firstName; } @Override public void setFirstName(String firstName) { this.firstName = firstName; } @Override public String getLastName() { return lastName; } @Override public void setLastName(String lastName) { this.lastName = lastName; } public String getBrokerSessionId() { return brokerSessionId; } public void setBrokerSessionId(String brokerSessionId) { this.brokerSessionId = brokerSessionId; } public String getBrokerUserId() { return brokerUserId; } public void setBrokerUserId(String brokerUserId) { this.brokerUserId = brokerUserId; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } public String getIdentityProviderId() { return identityProviderId; } public void setIdentityProviderId(String identityProviderId) { this.identityProviderId = identityProviderId; } public Map<String, ContextDataEntry> getContextData() { return contextData; } public void setContextData(Map<String, ContextDataEntry> contextData) { this.contextData = contextData; } @JsonIgnore @Override public Map<String, List<String>> getAttributes() { Map<String, List<String>> result = new HashMap<>(); for (Map.Entry<String, ContextDataEntry> entry : this.contextData.entrySet()) { if (entry.getKey().startsWith(Constants.USER_ATTRIBUTES_PREFIX)) { String attrName = entry.getKey().substring(16); // length of USER_ATTRIBUTES_PREFIX List<String> asList = getAttribute(attrName); result.put(attrName, asList); } } return result; } @JsonIgnore @Override public void setSingleAttribute(String name, String value) { List<String> list = new ArrayList<>(); list.add(value); setAttribute(name, list); } @JsonIgnore @Override public void setAttribute(String key, List<String> value) { try { byte[] listBytes = JsonSerialization.writeValueAsBytes(value); String listStr = Base64Url.encode(listBytes); ContextDataEntry ctxEntry = ContextDataEntry.create(List.class.getName(), listStr); this.contextData.put(Constants.USER_ATTRIBUTES_PREFIX + key, ctxEntry); } catch (IOException ioe) { throw new RuntimeException(ioe); } } @JsonIgnore @Override public List<String> getAttribute(String key) { ContextDataEntry ctxEntry = this.contextData.get(Constants.USER_ATTRIBUTES_PREFIX + key); if (ctxEntry != null) { try { String asString = ctxEntry.getData(); byte[] asBytes = Base64Url.decode(asString); List<String> asList = JsonSerialization.readValue(asBytes, List.class); return asList; } catch (IOException ioe) { throw new RuntimeException(ioe); } } else { return null; } } @JsonIgnore @Override public String getFirstAttribute(String name) { List<String> attrs = getAttribute(name); if (attrs == null || attrs.isEmpty()) { return null; } else { return attrs.get(0); } } public BrokeredIdentityContext deserialize(KeycloakSession session, AuthenticationSessionModel authSession) { BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId()); ctx.setUsername(getBrokerUsername()); ctx.setModelUsername(getModelUsername()); ctx.setEmail(getEmail()); ctx.setFirstName(getFirstName()); ctx.setLastName(getLastName()); ctx.setBrokerSessionId(getBrokerSessionId()); ctx.setBrokerUserId(getBrokerUserId()); ctx.setToken(getToken()); RealmModel realm = authSession.getRealm(); IdentityProviderModel idpConfig = realm.getIdentityProviderByAlias(getIdentityProviderId()); if (idpConfig == null) { throw new ModelException("Can't find identity provider with ID " + getIdentityProviderId() + " in realm " + realm.getName()); } IdentityProvider idp = IdentityBrokerService.getIdentityProvider(session, realm, idpConfig.getAlias()); ctx.setIdpConfig(idpConfig); ctx.setIdp(idp); IdentityProviderDataMarshaller serializer = idp.getMarshaller(); for (Map.Entry<String, ContextDataEntry> entry : getContextData().entrySet()) { try { ContextDataEntry value = entry.getValue(); Class<?> clazz = Reflections.classForName(value.getClazz(), this.getClass().getClassLoader()); Object deserialized = serializer.deserialize(value.getData(), clazz); ctx.getContextData().put(entry.getKey(), deserialized); } catch (Exception e) { throw new RuntimeException(e); } } ctx.setAuthenticationSession(authSession); return ctx; } public static SerializedBrokeredIdentityContext serialize(BrokeredIdentityContext context) { SerializedBrokeredIdentityContext ctx = new SerializedBrokeredIdentityContext(); ctx.setId(context.getId()); ctx.setBrokerUsername(context.getUsername()); ctx.setModelUsername(context.getModelUsername()); ctx.setEmail(context.getEmail()); ctx.setFirstName(context.getFirstName()); ctx.setLastName(context.getLastName()); ctx.setBrokerSessionId(context.getBrokerSessionId()); ctx.setBrokerUserId(context.getBrokerUserId()); ctx.setToken(context.getToken()); ctx.setIdentityProviderId(context.getIdpConfig().getAlias()); ctx.emailAsUsername = context.getAuthenticationSession().getRealm().isRegistrationEmailAsUsername(); IdentityProviderDataMarshaller serializer = context.getIdp().getMarshaller(); for (Map.Entry<String, Object> entry : context.getContextData().entrySet()) { Object value = entry.getValue(); String serializedValue = serializer.serialize(value); ContextDataEntry ctxEntry = ContextDataEntry.create(value.getClass().getName(), serializedValue); ctx.getContextData().put(entry.getKey(), ctxEntry); } return ctx; } // Save this context as note to authSession public void saveToAuthenticationSession(AuthenticationSessionModel authSession, String noteKey) { try { String asString = JsonSerialization.writeValueAsString(this); authSession.setAuthNote(noteKey, asString); } catch (IOException ioe) { throw new RuntimeException(ioe); } } public static SerializedBrokeredIdentityContext readFromAuthenticationSession(AuthenticationSessionModel authSession, String noteKey) { String asString = authSession.getAuthNote(noteKey); if (asString == null) { return null; } else { try { SerializedBrokeredIdentityContext serializedCtx = JsonSerialization.readValue(asString, SerializedBrokeredIdentityContext.class); serializedCtx.emailAsUsername = authSession.getRealm().isRegistrationEmailAsUsername(); return serializedCtx; } catch (IOException ioe) { throw new RuntimeException(ioe); } } } public static class ContextDataEntry { private String clazz; private String data; public String getClazz() { return clazz; } public void setClazz(String clazz) { this.clazz = clazz; } public String getData() { return data; } public void setData(String data) { this.data = data; } public static ContextDataEntry create(String clazz, String data) { ContextDataEntry entry = new ContextDataEntry(); entry.setClazz(clazz); entry.setData(data); return entry; } } }