merge
This commit is contained in:
commit
bfe9beae72
36 changed files with 741 additions and 98 deletions
|
@ -19,6 +19,7 @@ public class RealmRepresentation {
|
|||
protected boolean cookieLoginAllowed;
|
||||
protected boolean registrationAllowed;
|
||||
protected boolean social;
|
||||
protected boolean automaticRegistrationAfterSocialLogin;
|
||||
protected String privateKey;
|
||||
protected String publicKey;
|
||||
protected List<RoleRepresentation> roles;
|
||||
|
@ -29,6 +30,7 @@ public class RealmRepresentation {
|
|||
protected List<UserRepresentation> users;
|
||||
protected List<RoleMappingRepresentation> roleMappings;
|
||||
protected List<ScopeMappingRepresentation> scopeMappings;
|
||||
protected List<SocialMappingRepresentation> socialMappings;
|
||||
protected List<ApplicationRepresentation> applications;
|
||||
|
||||
|
||||
|
@ -144,6 +146,18 @@ public class RealmRepresentation {
|
|||
return mapping;
|
||||
}
|
||||
|
||||
public List<SocialMappingRepresentation> getSocialMappings() {
|
||||
return socialMappings;
|
||||
}
|
||||
|
||||
public SocialMappingRepresentation socialMapping(String username) {
|
||||
SocialMappingRepresentation mapping = new SocialMappingRepresentation();
|
||||
mapping.setUsername(username);
|
||||
if (socialMappings == null) socialMappings = new ArrayList<SocialMappingRepresentation>();
|
||||
socialMappings.add(mapping);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
public Set<String> getRequiredCredentials() {
|
||||
return requiredCredentials;
|
||||
}
|
||||
|
@ -223,4 +237,12 @@ public class RealmRepresentation {
|
|||
public void setSocial(boolean social) {
|
||||
this.social = social;
|
||||
}
|
||||
|
||||
public boolean isAutomaticRegistrationAfterSocialLogin() {
|
||||
return automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
|
||||
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
|
||||
this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SocialLinkRepresentation {
|
||||
|
||||
protected String socialProvider;
|
||||
protected String socialUsername;
|
||||
|
||||
public String getSocialProvider() {
|
||||
return socialProvider;
|
||||
}
|
||||
|
||||
public void setSocialProvider(String socialProvider) {
|
||||
this.socialProvider = socialProvider;
|
||||
}
|
||||
|
||||
public String getSocialUsername() {
|
||||
return socialUsername;
|
||||
}
|
||||
|
||||
public void setSocialUsername(String socialUsername) {
|
||||
this.socialUsername = socialUsername;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SocialMappingRepresentation {
|
||||
|
||||
protected String self; // link
|
||||
protected String username;
|
||||
protected List<SocialLinkRepresentation> socialLinks;
|
||||
|
||||
public String getSelf() {
|
||||
return self;
|
||||
}
|
||||
|
||||
public void setSelf(String self) {
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public List<SocialLinkRepresentation> getSocialLinks() {
|
||||
return socialLinks;
|
||||
}
|
||||
|
||||
public SocialLinkRepresentation socialLink(String socialProvider, String socialUsername) {
|
||||
SocialLinkRepresentation link = new SocialLinkRepresentation();
|
||||
link.setSocialProvider(socialProvider);
|
||||
link.setSocialUsername(socialUsername);
|
||||
if (socialLinks == null) socialLinks = new ArrayList<SocialLinkRepresentation>();
|
||||
socialLinks.add(link);
|
||||
return link;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
"sslNotRequired": true,
|
||||
"cookieLoginAllowed": true,
|
||||
"registrationAllowed": true,
|
||||
"social": true,
|
||||
"automaticRegistrationAfterSocialLogin": false,
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"requiredCredentials": [ "password" ],
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Map;
|
|||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.faces.bean.ManagedBean;
|
||||
import javax.faces.bean.ManagedProperty;
|
||||
import javax.faces.bean.RequestScoped;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -42,6 +43,8 @@ public class RegisterBean {
|
|||
|
||||
private HashMap<String, String> formData;
|
||||
|
||||
private boolean socialRegistration;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
FacesContext ctx = FacesContext.getCurrentInstance();
|
||||
|
@ -49,6 +52,9 @@ public class RegisterBean {
|
|||
|
||||
this.formData = new HashMap<String, String>();
|
||||
|
||||
Boolean socialRegistrationAttr = (Boolean)request.getAttribute(FormFlows.SOCIAL_REGISTRATION);
|
||||
this.socialRegistration = socialRegistrationAttr != null && socialRegistrationAttr;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
MultivaluedMap<String, String> formData = (MultivaluedMap<String, String>) request.getAttribute(FormFlows.DATA);
|
||||
if (formData != null) {
|
||||
|
@ -62,4 +68,8 @@ public class RegisterBean {
|
|||
return formData;
|
||||
}
|
||||
|
||||
public boolean isSocialRegistration() {
|
||||
return socialRegistration;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,6 +46,9 @@ public class SocialBean {
|
|||
@ManagedProperty(value = "#{realm}")
|
||||
private RealmBean realm;
|
||||
|
||||
@ManagedProperty(value = "#{register}")
|
||||
private RegisterBean registerBean;
|
||||
|
||||
@ManagedProperty(value = "#{url}")
|
||||
private UrlBean url;
|
||||
|
||||
|
@ -73,4 +76,32 @@ public class SocialBean {
|
|||
return providers;
|
||||
}
|
||||
|
||||
// Display panel with social providers just in case that social is enabled for realm, but we are not in the middle of registration with social
|
||||
public boolean isDisplaySocialProviders() {
|
||||
return realm.isSocial() && !registerBean.isSocialRegistration();
|
||||
}
|
||||
|
||||
public RealmBean getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(RealmBean realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public UrlBean getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(UrlBean url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public RegisterBean getRegisterBean() {
|
||||
return registerBean;
|
||||
}
|
||||
|
||||
public void setRegisterBean(RegisterBean registerBean) {
|
||||
this.registerBean = registerBean;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,9 @@ public class UrlBean {
|
|||
@ManagedProperty(value = "#{realm}")
|
||||
private RealmBean realm;
|
||||
|
||||
@ManagedProperty(value = "#{register}")
|
||||
private RegisterBean registerBean;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
FacesContext ctx = FacesContext.getCurrentInstance();
|
||||
|
@ -64,6 +67,14 @@ public class UrlBean {
|
|||
this.realm = realm;
|
||||
}
|
||||
|
||||
public RegisterBean getRegisterBean() {
|
||||
return registerBean;
|
||||
}
|
||||
|
||||
public void setRegisterBean(RegisterBean registerBean) {
|
||||
this.registerBean = registerBean;
|
||||
}
|
||||
|
||||
public String getAccessUrl() {
|
||||
return Urls.accountAccessPage(baseURI, realm.getId()).toString();
|
||||
}
|
||||
|
@ -98,7 +109,10 @@ public class UrlBean {
|
|||
|
||||
public String getRegistrationAction() {
|
||||
if (realm.isSaas()) {
|
||||
// TODO: saas social registration
|
||||
return Urls.saasRegisterAction(baseURI).toString();
|
||||
} else if (registerBean.isSocialRegistration()) {
|
||||
return Urls.socialRegisterAction(baseURI, realm.getId()).toString();
|
||||
} else {
|
||||
return Urls.realmRegisterAction(baseURI, realm.getId()).toString();
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
</ui:define>
|
||||
|
||||
<ui:define name="info">
|
||||
<p>#{messages.alreadyHaveAccount} <a href="#{url.loginUrl}">#{messages.logIn}</a>.</p>
|
||||
<h:panelGroup rendered="#{not register.socialRegistration}">
|
||||
<p>#{messages.alreadyHaveAccount} <a href="#{url.loginUrl}">#{messages.logIn}</a>.</p>
|
||||
</h:panelGroup>
|
||||
</ui:define>
|
||||
</ui:composition>
|
|
@ -39,12 +39,12 @@ body {
|
|||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
<h:panelGroup rendered="#{realm.social}">
|
||||
<h:panelGroup rendered="#{social.displaySocialProviders}">
|
||||
<section class="social-login"> <span>or</span>
|
||||
<h3>Social login area</h3>
|
||||
<p>#{messages.logInWith}</p>
|
||||
<ul>
|
||||
<ui:repeat var="p" value="#{forms.providers}">
|
||||
<ui:repeat var="p" value="#{social.providers}">
|
||||
<li><a href="#{p.loginUrl}" class="zocial #{p.id}"> <span class="text">#{p.name}</span></a></li>
|
||||
</ui:repeat>
|
||||
</ul>
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -236,7 +236,7 @@
|
|||
<dependency>
|
||||
<groupId>org.jboss.arquillian.extension</groupId>
|
||||
<artifactId>arquillian-drone-bom</artifactId>
|
||||
<version>1.2.0.Alpha3</version>
|
||||
<version>1.2.0.Beta1</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
|
|
@ -74,6 +74,7 @@ public class RealmManager {
|
|||
realm.setSocial(rep.isSocial());
|
||||
realm.setCookieLoginAllowed(rep.isCookieLoginAllowed());
|
||||
realm.setRegistrationAllowed(rep.isRegistrationAllowed());
|
||||
realm.setAutomaticRegistrationAfterSocialLogin(rep.isAutomaticRegistrationAfterSocialLogin());
|
||||
realm.setSslNotRequired((rep.isSslNotRequired()));
|
||||
realm.setAccessCodeLifespan(rep.getAccessCodeLifespan());
|
||||
realm.setTokenLifespan(rep.getTokenLifespan());
|
||||
|
@ -109,6 +110,7 @@ public class RealmManager {
|
|||
newRealm.setSslNotRequired(rep.isSslNotRequired());
|
||||
newRealm.setCookieLoginAllowed(rep.isCookieLoginAllowed());
|
||||
newRealm.setRegistrationAllowed(rep.isRegistrationAllowed());
|
||||
newRealm.setAutomaticRegistrationAfterSocialLogin(rep.isAutomaticRegistrationAfterSocialLogin());
|
||||
if (rep.getPrivateKey() == null || rep.getPublicKey() == null) {
|
||||
generateRealmKeys(newRealm);
|
||||
} else {
|
||||
|
@ -185,6 +187,16 @@ public class RealmManager {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
if (rep.getSocialMappings() != null) {
|
||||
for (SocialMappingRepresentation socialMapping : rep.getSocialMappings()) {
|
||||
UserModel user = userMap.get(socialMapping.getUsername());
|
||||
for (SocialLinkRepresentation link : socialMapping.getSocialLinks()) {
|
||||
SocialLinkModel mappingModel = new SocialLinkModel(link.getSocialProvider(), link.getSocialUsername());
|
||||
newRealm.addSocialLink(user, mappingModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
|
||||
|
@ -245,6 +257,7 @@ public class RealmManager {
|
|||
rep.setRealm(realm.getName());
|
||||
rep.setEnabled(realm.isEnabled());
|
||||
rep.setSocial(realm.isSocial());
|
||||
rep.setAutomaticRegistrationAfterSocialLogin(realm.isAutomaticRegistrationAfterSocialLogin());
|
||||
rep.setSslNotRequired(realm.isSslNotRequired());
|
||||
rep.setCookieLoginAllowed(realm.isCookieLoginAllowed());
|
||||
rep.setPublicKey(realm.getPublicKeyPem());
|
||||
|
|
|
@ -127,9 +127,19 @@ public interface RealmModel {
|
|||
|
||||
void updateRequiredApplicationCredentials(Set<String> creds);
|
||||
|
||||
UserModel getUserBySocialLink(SocialLinkModel socialLink);
|
||||
|
||||
Set<SocialLinkModel> getSocialLinks(UserModel user);
|
||||
|
||||
void addSocialLink(UserModel user, SocialLinkModel socialLink);
|
||||
|
||||
void removeSocialLink(UserModel user, SocialLinkModel socialLink);
|
||||
|
||||
boolean isSocial();
|
||||
|
||||
void setSocial(boolean social);
|
||||
|
||||
List<UserModel> queryUsers(Map<String, String> parameters);
|
||||
public boolean isAutomaticRegistrationAfterSocialLogin();
|
||||
|
||||
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package org.keycloak.services.models;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SocialLinkModel {
|
||||
|
||||
private String socialUsername;
|
||||
private String socialProvider;
|
||||
|
||||
public SocialLinkModel(String socialProvider, String socialUsername) {
|
||||
this.socialUsername = socialUsername;
|
||||
this.socialProvider = socialProvider;
|
||||
}
|
||||
|
||||
public String getSocialUsername() {
|
||||
return socialUsername;
|
||||
}
|
||||
|
||||
public void setSocialUsername(String socialUsername) {
|
||||
this.socialUsername = socialUsername;
|
||||
}
|
||||
|
||||
public String getSocialProvider() {
|
||||
return socialProvider;
|
||||
}
|
||||
|
||||
public void setSocialProvider(String socialProvider) {
|
||||
this.socialProvider = socialProvider;
|
||||
}
|
||||
}
|
|
@ -5,15 +5,27 @@ import org.jboss.resteasy.logging.Logger;
|
|||
import org.jboss.resteasy.security.PemUtils;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.models.*;
|
||||
import org.keycloak.services.models.picketlink.mappings.ApplicationData;
|
||||
import org.keycloak.services.models.KeycloakSession;
|
||||
import org.keycloak.services.models.RealmModel;
|
||||
import org.keycloak.services.models.RequiredCredentialModel;
|
||||
import org.keycloak.services.models.ApplicationModel;
|
||||
import org.keycloak.services.models.RoleModel;
|
||||
import org.keycloak.services.models.SocialLinkModel;
|
||||
import org.keycloak.services.models.UserCredentialModel;
|
||||
import org.keycloak.services.models.UserModel;
|
||||
import org.keycloak.services.models.picketlink.mappings.RealmData;
|
||||
import org.keycloak.services.models.picketlink.mappings.ApplicationData;
|
||||
import org.keycloak.services.models.picketlink.relationships.*;
|
||||
import org.keycloak.services.models.picketlink.relationships.RequiredApplicationCredentialRelationship;
|
||||
import org.picketlink.idm.IdentityManager;
|
||||
import org.picketlink.idm.PartitionManager;
|
||||
import org.picketlink.idm.RelationshipManager;
|
||||
import org.picketlink.idm.credential.*;
|
||||
import org.picketlink.idm.model.AttributedType;
|
||||
import org.picketlink.idm.credential.Credentials;
|
||||
import org.picketlink.idm.credential.Password;
|
||||
import org.picketlink.idm.credential.TOTPCredential;
|
||||
import org.picketlink.idm.credential.TOTPCredentials;
|
||||
import org.picketlink.idm.credential.UsernamePasswordCredentials;
|
||||
import org.picketlink.idm.credential.X509CertificateCredentials;
|
||||
import org.picketlink.idm.model.IdentityType;
|
||||
import org.picketlink.idm.model.sample.Grant;
|
||||
import org.picketlink.idm.model.sample.Role;
|
||||
|
@ -27,7 +39,13 @@ import java.io.StringWriter;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Meant to be a per-request object
|
||||
|
@ -104,6 +122,17 @@ public class RealmAdapter implements RealmModel {
|
|||
realm.setSocial(social);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutomaticRegistrationAfterSocialLogin() {
|
||||
return realm.isAutomaticRegistrationAfterSocialLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
|
||||
realm.setAutomaticRegistrationAfterSocialLogin(automaticRegistrationAfterSocialLogin);
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSslNotRequired() {
|
||||
return realm.isSslNotRequired();
|
||||
|
@ -459,21 +488,6 @@ public class RealmAdapter implements RealmModel {
|
|||
return new UserAdapter(user, getIdm());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> queryUsers(Map<String, String> parameters) {
|
||||
IdentityQuery<User> userQuery = getIdm().createIdentityQuery(User.class);
|
||||
for (Map.Entry<String, String> entry : parameters.entrySet()) {
|
||||
userQuery.setParameter(AttributedType.QUERY_ATTRIBUTE.byName(entry.getKey()), entry.getValue());
|
||||
}
|
||||
List<User> users = userQuery.getResultList();
|
||||
List<UserModel> userModels = new ArrayList<UserModel>();
|
||||
for (User user : users) {
|
||||
userModels.add(new UserAdapter(user, getIdm()));
|
||||
}
|
||||
return userModels;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleAdapter getRole(String name) {
|
||||
Role role = SampleModel.getRole(getIdm(), name);
|
||||
|
@ -691,4 +705,54 @@ public class RealmAdapter implements RealmModel {
|
|||
realm.setDefaultRoles(defaultRoles);
|
||||
updateRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
|
||||
RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
|
||||
query.setParameter(SocialLinkRelationship.SOCIAL_PROVIDER, socialLink.getSocialProvider());
|
||||
query.setParameter(SocialLinkRelationship.SOCIAL_USERNAME, socialLink.getSocialUsername());
|
||||
List<SocialLinkRelationship> results = query.getResultList();
|
||||
if (results.isEmpty()) {
|
||||
return null;
|
||||
} else if (results.size() > 1) {
|
||||
throw new IllegalStateException("More results found for socialProvider=" + socialLink.getSocialProvider() +
|
||||
", socialUsername=" + socialLink.getSocialUsername() + ", results=" + results);
|
||||
} else {
|
||||
User user = results.get(0).getUser();
|
||||
return new UserAdapter(user, getIdm());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<SocialLinkModel> getSocialLinks(UserModel user) {
|
||||
RelationshipQuery<SocialLinkRelationship> query = getRelationshipManager().createRelationshipQuery(SocialLinkRelationship.class);
|
||||
query.setParameter(SocialLinkRelationship.USER, ((UserAdapter)user).getUser());
|
||||
List<SocialLinkRelationship> plSocialLinks = query.getResultList();
|
||||
|
||||
Set<SocialLinkModel> results = new HashSet<SocialLinkModel>();
|
||||
for (SocialLinkRelationship relationship : plSocialLinks) {
|
||||
results.add(new SocialLinkModel(relationship.getSocialProvider(), relationship.getSocialUsername()));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSocialLink(UserModel user, SocialLinkModel socialLink) {
|
||||
SocialLinkRelationship relationship = new SocialLinkRelationship();
|
||||
relationship.setUser(((UserAdapter)user).getUser());
|
||||
relationship.setSocialProvider(socialLink.getSocialProvider());
|
||||
relationship.setSocialUsername(socialLink.getSocialUsername());
|
||||
|
||||
getRelationshipManager().add(relationship);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSocialLink(UserModel user, SocialLinkModel socialLink) {
|
||||
SocialLinkRelationship relationship = new SocialLinkRelationship();
|
||||
relationship.setUser(((UserAdapter)user).getUser());
|
||||
relationship.setSocialProvider(socialLink.getSocialProvider());
|
||||
relationship.setSocialUsername(socialLink.getSocialUsername());
|
||||
|
||||
getRelationshipManager().remove(relationship);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ public class RealmData extends AbstractPartition {
|
|||
private boolean cookieLoginAllowed;
|
||||
private boolean registrationAllowed;
|
||||
private boolean social;
|
||||
private boolean automaticRegistrationAfterSocialLogin;
|
||||
private int tokenLifespan;
|
||||
private int accessCodeLifespan;
|
||||
private String publicKeyPem;
|
||||
|
@ -54,6 +55,15 @@ public class RealmData extends AbstractPartition {
|
|||
this.social = social;
|
||||
}
|
||||
|
||||
@AttributeProperty
|
||||
public boolean isAutomaticRegistrationAfterSocialLogin() {
|
||||
return automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
|
||||
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
|
||||
this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
|
||||
@AttributeProperty
|
||||
public boolean isSslNotRequired() {
|
||||
return sslNotRequired;
|
||||
|
|
|
@ -37,6 +37,8 @@ public class RealmEntity implements Serializable {
|
|||
@AttributeValue
|
||||
private boolean social;
|
||||
@AttributeValue
|
||||
private boolean automaticRegistrationAfterSocialLogin;
|
||||
@AttributeValue
|
||||
private int tokenLifespan;
|
||||
@AttributeValue
|
||||
private int accessCodeLifespan;
|
||||
|
@ -106,6 +108,14 @@ public class RealmEntity implements Serializable {
|
|||
this.social = social;
|
||||
}
|
||||
|
||||
public boolean isAutomaticRegistrationAfterSocialLogin() {
|
||||
return automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
|
||||
public void setAutomaticRegistrationAfterSocialLogin(boolean automaticRegistrationAfterSocialLogin) {
|
||||
this.automaticRegistrationAfterSocialLogin = automaticRegistrationAfterSocialLogin;
|
||||
}
|
||||
|
||||
public int getTokenLifespan() {
|
||||
return tokenLifespan;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package org.keycloak.services.models.picketlink.relationships;
|
||||
|
||||
import org.picketlink.idm.model.AbstractAttributedType;
|
||||
import org.picketlink.idm.model.Attribute;
|
||||
import org.picketlink.idm.model.Relationship;
|
||||
import org.picketlink.idm.model.sample.User;
|
||||
import org.picketlink.idm.query.AttributeParameter;
|
||||
import org.picketlink.idm.query.RelationshipQueryParameter;
|
||||
|
||||
/**
|
||||
* Binding between user and his social username for particular Social provider
|
||||
*
|
||||
* Example: Keycloak user "john" has username "john123" in social provider "facebook"
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SocialLinkRelationship extends AbstractAttributedType implements Relationship {
|
||||
|
||||
private static final long serialVersionUID = 154879L;
|
||||
|
||||
public static final AttributeParameter SOCIAL_PROVIDER = new AttributeParameter("socialProvider");
|
||||
public static final AttributeParameter SOCIAL_USERNAME = new AttributeParameter("socialUsername");
|
||||
|
||||
public static final RelationshipQueryParameter USER = new RelationshipQueryParameter() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "user";
|
||||
}
|
||||
};
|
||||
|
||||
private User user;
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getSocialProvider() {
|
||||
return (String)getAttribute("socialProvider").getValue();
|
||||
}
|
||||
|
||||
public void setSocialProvider(String socialProvider) {
|
||||
setAttribute(new Attribute<String>("socialProvider", socialProvider));
|
||||
}
|
||||
|
||||
public String getSocialUsername() {
|
||||
return (String)getAttribute("socialUsername").getValue();
|
||||
}
|
||||
|
||||
public void setSocialUsername(String socialProviderUserId) {
|
||||
setAttribute(new Attribute<String>("socialUsername", socialProviderUserId));
|
||||
}
|
||||
}
|
|
@ -21,36 +21,56 @@
|
|||
*/
|
||||
package org.keycloak.services.resources;
|
||||
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.models.KeycloakSession;
|
||||
import org.keycloak.services.models.RealmModel;
|
||||
import org.keycloak.services.models.RoleModel;
|
||||
import org.keycloak.services.models.UserModel;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.services.resources.flows.OAuthFlows;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
import org.keycloak.social.*;
|
||||
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.container.ResourceContext;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
|
||||
import org.jboss.resteasy.logging.Logger;
|
||||
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
|
||||
import org.jboss.resteasy.spi.HttpRequest;
|
||||
import org.jboss.resteasy.spi.HttpResponse;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.models.*;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.services.resources.flows.OAuthFlows;
|
||||
import org.keycloak.services.resources.flows.PageFlows;
|
||||
import org.keycloak.services.resources.flows.Urls;
|
||||
import org.keycloak.social.AuthCallback;
|
||||
import org.keycloak.social.AuthRequest;
|
||||
import org.keycloak.social.RequestDetails;
|
||||
import org.keycloak.social.RequestDetailsBuilder;
|
||||
import org.keycloak.social.SocialConstants;
|
||||
import org.keycloak.social.SocialProvider;
|
||||
import org.keycloak.social.SocialProviderConfig;
|
||||
import org.keycloak.social.SocialProviderException;
|
||||
import org.keycloak.social.SocialRequestManager;
|
||||
import org.keycloak.social.SocialUser;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -69,14 +89,21 @@ public class SocialResource {
|
|||
@Context
|
||||
private HttpRequest request;
|
||||
|
||||
@Context
|
||||
private HttpResponse response;
|
||||
|
||||
@Context
|
||||
ResourceContext resourceContext;
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
protected SocialRequestManager socialRequestManager;
|
||||
|
||||
protected TokenManager tokenManager;
|
||||
private SocialRequestManager socialRequestManager;
|
||||
|
||||
protected AuthenticationManager authManager = new AuthenticationManager();
|
||||
private TokenManager tokenManager;
|
||||
|
||||
private AuthenticationManager authManager = new AuthenticationManager();
|
||||
|
||||
public SocialResource(TokenManager tokenManager, SocialRequestManager socialRequestManager) {
|
||||
this.tokenManager = tokenManager;
|
||||
|
@ -102,10 +129,6 @@ public class SocialResource {
|
|||
return oauth.forwardToSecurityFailure("Realm not enabled.");
|
||||
}
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
return oauth.forwardToSecurityFailure("Realm not enabled.");
|
||||
}
|
||||
|
||||
String clientId = requestData.getClientAttributes().get("clientId");
|
||||
|
||||
UserModel client = realm.getUser(clientId);
|
||||
|
@ -131,21 +154,54 @@ public class SocialResource {
|
|||
return oauth.forwardToSecurityFailure("Failed to process social callback");
|
||||
}
|
||||
|
||||
// TODO Lookup user based on attribute for provider id - this is so a user can have a friendly username + link a
|
||||
// user to
|
||||
// multiple social logins
|
||||
UserModel user = realm.getUser(provider.getId() + "." + socialUser.getId());
|
||||
SocialLinkModel socialLink = new SocialLinkModel(provider.getId(), socialUser.getUsername());
|
||||
UserModel user = realm.getUserBySocialLink(socialLink);
|
||||
|
||||
if (user == null) {
|
||||
if (!realm.isRegistrationAllowed()) {
|
||||
return oauth.forwardToSecurityFailure("Registration not allowed");
|
||||
}
|
||||
|
||||
user = realm.addUser(provider.getId() + "." + socialUser.getId());
|
||||
user.setAttribute(provider.getId() + ".id", socialUser.getId());
|
||||
// Automatically register user into realm with his social username (don't redirect to registration screen)
|
||||
if (realm.isAutomaticRegistrationAfterSocialLogin()) {
|
||||
|
||||
for (RoleModel role : realm.getDefaultRoles()) {
|
||||
realm.grantRole(user, role);
|
||||
if (realm.getUser(socialUser.getUsername()) != null) {
|
||||
// TODO: Username is already in realm. Show message and let user to bind accounts after he re-authenticate
|
||||
throw new IllegalStateException("Username " + socialUser.getUsername() +
|
||||
" already registered in the realm. TODO: bind accounts...");
|
||||
|
||||
// TODO: Maybe we should search also by email and bind accounts if user with this email is
|
||||
// already registered. But actually Keycloak allows duplicate emails
|
||||
} else {
|
||||
user = realm.addUser(socialUser.getUsername());
|
||||
user.setFirstName(socialUser.getFirstName());
|
||||
user.setLastName(socialUser.getLastName());
|
||||
user.setEmail(socialUser.getEmail());
|
||||
}
|
||||
|
||||
realm.addSocialLink(user, socialLink);
|
||||
|
||||
for (RoleModel role : realm.getDefaultRoles()) {
|
||||
realm.grantRole(user, role);
|
||||
}
|
||||
} else {
|
||||
// Redirect user to registration screen with prefilled data from social provider
|
||||
MultivaluedMap<String, String> formData = fillRegistrationFormWithSocialData(socialUser);
|
||||
|
||||
RequestDetailsBuilder reqDetailsBuilder = RequestDetailsBuilder.createFromRequestDetails(requestData);
|
||||
reqDetailsBuilder.putSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK, socialLink);
|
||||
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
socialRequestManager.addRequest(requestId, reqDetailsBuilder.build());
|
||||
boolean secureOnly = !realm.isSslNotRequired();
|
||||
String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
|
||||
logger.info("creating cookie for social registration - name: " + SocialConstants.SOCIAL_REGISTRATION_COOKIE
|
||||
+ " path: " + cookiePath);
|
||||
NewCookie newCookie = new NewCookie(SocialConstants.SOCIAL_REGISTRATION_COOKIE, requestId,
|
||||
cookiePath, null, "Added social cookie", NewCookie.DEFAULT_MAX_AGE, secureOnly);
|
||||
response.addNewCookie(newCookie);
|
||||
|
||||
return Flows.forms(realm, request).setFormData(formData).setSocialRegistration(true).forwardToRegistration();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,6 +249,66 @@ public class SocialResource {
|
|||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{realm}/socialRegistration")
|
||||
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
public Response socialRegistration(@PathParam("realm") final String realmId,
|
||||
final MultivaluedMap<String, String> formData) {
|
||||
PageFlows pageFlows = Flows.pages(request);
|
||||
Cookie cookie = headers.getCookies().get(SocialConstants.SOCIAL_REGISTRATION_COOKIE);
|
||||
if (cookie == null) {
|
||||
return pageFlows.forwardToSecurityFailure("Social registration cookie not found");
|
||||
}
|
||||
|
||||
String requestId = cookie.getValue();
|
||||
if (!socialRequestManager.isRequestId(requestId)) {
|
||||
logger.error("Unknown requestId found in cookie. Maybe it's expired. requestId=" + requestId);
|
||||
return pageFlows.forwardToSecurityFailure("Unknown requestId found in cookie. Maybe it's expired.");
|
||||
}
|
||||
|
||||
RequestDetails requestData = socialRequestManager.getData(requestId);
|
||||
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
RealmModel realm = realmManager.getRealm(realmId);
|
||||
if (realm == null || !realm.isEnabled()) {
|
||||
return pageFlows.forwardToSecurityFailure("Realm doesn't exists or is not enabled.");
|
||||
}
|
||||
TokenService tokenService = new TokenService(realm, tokenManager);
|
||||
resourceContext.initResource(tokenService);
|
||||
|
||||
String clientId = requestData.getClientAttribute("clientId");
|
||||
String scope = requestData.getClientAttribute("scope");
|
||||
String state = requestData.getClientAttribute("state");
|
||||
String redirectUri = requestData.getClientAttribute("redirectUri");
|
||||
SocialLinkModel socialLink = (SocialLinkModel)requestData.getSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK);
|
||||
|
||||
Response response1 = tokenService.processRegisterImpl(clientId, scope, state, redirectUri, formData, true);
|
||||
|
||||
// Some error occured during registration
|
||||
if (response1 != null || request.wasForwarded()) {
|
||||
logger.warn("Registration attempt wasn't successful. Request already forwarded or redirected.");
|
||||
return response1;
|
||||
}
|
||||
|
||||
String username = formData.getFirst("username");
|
||||
UserModel user = realm.getUser(username);
|
||||
if (user == null) {
|
||||
// Normally shouldn't happen
|
||||
throw new IllegalStateException("User " + username + " not found in the realm");
|
||||
}
|
||||
realm.addSocialLink(user, socialLink);
|
||||
|
||||
// Expire cookie and invalidate requestData
|
||||
String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
|
||||
NewCookie newCookie = new NewCookie(SocialConstants.SOCIAL_REGISTRATION_COOKIE, "", cookiePath, null,
|
||||
"Expire social cookie", 0, false);
|
||||
logger.info("Expiring social registration cookie: " + SocialConstants.SOCIAL_REGISTRATION_COOKIE + ", path: " + cookiePath);
|
||||
response.addNewCookie(newCookie);
|
||||
socialRequestManager.retrieveData(requestId);
|
||||
|
||||
return tokenService.processLogin(clientId, scope, state, redirectUri, formData);
|
||||
}
|
||||
|
||||
private RequestDetails getRequestDetails(Map<String, String[]> queryParams) {
|
||||
Iterator<SocialProvider> itr = ServiceRegistry.lookupProviders(SocialProvider.class);
|
||||
|
||||
|
@ -231,4 +347,25 @@ public class SocialResource {
|
|||
return queryParams;
|
||||
}
|
||||
|
||||
protected MultivaluedMap<String, String> fillRegistrationFormWithSocialData(SocialUser socialUser) {
|
||||
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
|
||||
formData.putSingle("username", socialUser.getUsername());
|
||||
|
||||
if (socialUser.getEmail() != null) {
|
||||
formData.putSingle("email", socialUser.getEmail());
|
||||
}
|
||||
|
||||
String fullName = null;
|
||||
if (socialUser.getFirstName() == null) {
|
||||
fullName = socialUser.getLastName();
|
||||
} else if (socialUser.getLastName() == null) {
|
||||
fullName = socialUser.getFirstName();
|
||||
} else {
|
||||
fullName = socialUser.getFirstName() + " " + socialUser.getLastName();
|
||||
}
|
||||
|
||||
formData.putSingle("name", fullName);
|
||||
return formData;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,18 +11,44 @@ import org.jboss.resteasy.spi.HttpResponse;
|
|||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.SkeletonKeyToken;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.*;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.services.managers.AccessCodeEntry;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.ResourceAdminManager;
|
||||
import org.keycloak.services.managers.TokenManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.models.*;
|
||||
import org.keycloak.services.resources.flows.Flows;
|
||||
import org.keycloak.services.resources.flows.OAuthFlows;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
import org.picketlink.idm.credential.util.TimeBasedOTP;
|
||||
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.*;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.ForbiddenException;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.NotAuthorizedException;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.NewCookie;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import javax.ws.rs.ext.Providers;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -38,25 +64,18 @@ public class TokenService {
|
|||
|
||||
@Context
|
||||
protected Providers providers;
|
||||
|
||||
@Context
|
||||
protected SecurityContext securityContext;
|
||||
|
||||
@Context
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
@Context
|
||||
protected HttpHeaders headers;
|
||||
|
||||
@Context
|
||||
protected HttpRequest request;
|
||||
|
||||
@Context
|
||||
protected HttpResponse response;
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
|
||||
@Context
|
||||
protected KeycloakTransaction transaction;
|
||||
|
||||
|
@ -215,6 +234,19 @@ public class TokenService {
|
|||
public Response processRegister(@QueryParam("client_id") final String clientId,
|
||||
@QueryParam("scope") final String scopeParam, @QueryParam("state") final String state,
|
||||
@QueryParam("redirect_uri") final String redirect, final MultivaluedMap<String, String> formData) {
|
||||
Response registrationResponse = processRegisterImpl(clientId, scopeParam, state, redirect, formData, false);
|
||||
|
||||
// If request has been already forwarded (either due to security or validation error) then we won't continue with login
|
||||
if (registrationResponse != null || request.wasForwarded()) {
|
||||
logger.warn("Registration attempt wasn't successful. Request already forwarded or redirected.");
|
||||
return registrationResponse;
|
||||
} else {
|
||||
return processLogin(clientId, scopeParam, state, redirect, formData);
|
||||
}
|
||||
}
|
||||
|
||||
public Response processRegisterImpl(String clientId, String scopeParam, String state, String redirect,
|
||||
MultivaluedMap<String, String> formData, boolean isSocialRegistration) {
|
||||
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
|
||||
|
||||
if (!realm.isEnabled()) {
|
||||
|
@ -240,7 +272,8 @@ public class TokenService {
|
|||
|
||||
String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
|
||||
if (error != null) {
|
||||
return Flows.forms(realm, request).setError(error).setFormData(formData).forwardToRegistration();
|
||||
return Flows.forms(realm, request).setError(error).setFormData(formData)
|
||||
.setSocialRegistration(isSocialRegistration).forwardToRegistration();
|
||||
}
|
||||
|
||||
String username = formData.getFirst("username");
|
||||
|
@ -248,7 +281,7 @@ public class TokenService {
|
|||
UserModel user = realm.getUser(username);
|
||||
if (user != null) {
|
||||
return Flows.forms(realm, request).setError(Messages.USERNAME_EXISTS).setFormData(formData)
|
||||
.forwardToRegistration();
|
||||
.setSocialRegistration(isSocialRegistration).forwardToRegistration();
|
||||
}
|
||||
|
||||
user = realm.addUser(username);
|
||||
|
@ -290,7 +323,7 @@ public class TokenService {
|
|||
realm.grantRole(user, role);
|
||||
}
|
||||
|
||||
return processLogin(clientId, scopeParam, state, redirect, formData);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Path("access/codes")
|
||||
|
|
|
@ -38,6 +38,7 @@ public class FormFlows {
|
|||
public static final String ERROR_MESSAGE = "KEYCLOAK_FORMS_ERROR_MESSAGE";
|
||||
public static final String REALM = Realm.class.getName();
|
||||
public static final String USER = UserModel.class.getName();
|
||||
public static final String SOCIAL_REGISTRATION = "socialRegistration";
|
||||
|
||||
private String error;
|
||||
private MultivaluedMap<String, String> formData;
|
||||
|
@ -47,6 +48,8 @@ public class FormFlows {
|
|||
private HttpRequest request;
|
||||
private UserModel userModel;
|
||||
|
||||
private boolean socialRegistration;
|
||||
|
||||
FormFlows(RealmModel realm, HttpRequest request) {
|
||||
this.realm = realm;
|
||||
this.request = request;
|
||||
|
@ -75,6 +78,8 @@ public class FormFlows {
|
|||
request.setAttribute(USER, userModel);
|
||||
}
|
||||
|
||||
request.setAttribute(SOCIAL_REGISTRATION, socialRegistration);
|
||||
|
||||
request.forward(form);
|
||||
return null;
|
||||
}
|
||||
|
@ -113,6 +118,12 @@ public class FormFlows {
|
|||
return this;
|
||||
}
|
||||
|
||||
// Set flag whether user registration is triggered from social login
|
||||
public FormFlows setSocialRegistration(boolean socialRegistration) {
|
||||
this.socialRegistration = socialRegistration;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FormFlows setFormData(MultivaluedMap<String, String> formData) {
|
||||
this.formData = formData;
|
||||
return this;
|
||||
|
|
|
@ -95,7 +95,7 @@ public class Urls {
|
|||
return saasBase(baseUri).path(SaasService.class, "registerPage").build();
|
||||
}
|
||||
|
||||
private static UriBuilder socialBase(URI baseUri) {
|
||||
public static UriBuilder socialBase(URI baseUri) {
|
||||
return UriBuilder.fromUri(baseUri).path(SocialResource.class);
|
||||
}
|
||||
|
||||
|
@ -111,4 +111,8 @@ public class Urls {
|
|||
private static UriBuilder tokenBase(URI baseUri) {
|
||||
return realmBase(baseUri).path(RealmsResource.class, "getTokenService");
|
||||
}
|
||||
|
||||
public static URI socialRegisterAction(URI baseUri, String realmId) {
|
||||
return socialBase(baseUri).path(SocialResource.class, "socialRegistration").build(realmId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ public class AdapterTest {
|
|||
realmModel.setPrivateKeyPem("0234234");
|
||||
realmModel.setPublicKeyPem("0234234");
|
||||
realmModel.setTokenLifespan(1000);
|
||||
realmModel.setAutomaticRegistrationAfterSocialLogin(true);
|
||||
realmModel.addDefaultRole("foo");
|
||||
|
||||
System.out.println(realmModel.getId());
|
||||
|
@ -86,6 +87,7 @@ public class AdapterTest {
|
|||
Assert.assertEquals(realmModel.getName(), "JUGGLER");
|
||||
Assert.assertEquals(realmModel.getPrivateKeyPem(), "0234234");
|
||||
Assert.assertEquals(realmModel.getPublicKeyPem(), "0234234");
|
||||
Assert.assertEquals(realmModel.isAutomaticRegistrationAfterSocialLogin(), true);
|
||||
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
|
||||
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0).getName());
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.keycloak.services.models.RealmModel;
|
|||
import org.keycloak.services.models.RequiredCredentialModel;
|
||||
import org.keycloak.services.models.ApplicationModel;
|
||||
import org.keycloak.services.models.RoleModel;
|
||||
import org.keycloak.services.models.SocialLinkModel;
|
||||
import org.keycloak.services.models.UserModel;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.services.resources.SaasService;
|
||||
|
@ -58,6 +59,7 @@ public class ImportTest {
|
|||
defaultRealm.setSslNotRequired(false);
|
||||
defaultRealm.setCookieLoginAllowed(true);
|
||||
defaultRealm.setRegistrationAllowed(true);
|
||||
defaultRealm.setAutomaticRegistrationAfterSocialLogin(false);
|
||||
manager.generateRealmKeys(defaultRealm);
|
||||
defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||
RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
|
||||
|
@ -68,6 +70,8 @@ public class ImportTest {
|
|||
RealmModel realm = manager.createRealm("demo", rep.getRealm());
|
||||
manager.importRealm(rep, realm);
|
||||
realm.addRealmAdmin(admin);
|
||||
|
||||
Assert.assertFalse(realm.isAutomaticRegistrationAfterSocialLogin());
|
||||
List<RequiredCredentialModel> creds = realm.getRequiredCredentials();
|
||||
Assert.assertEquals(1, creds.size());
|
||||
RequiredCredentialModel cred = creds.get(0);
|
||||
|
@ -82,6 +86,8 @@ public class ImportTest {
|
|||
Set<String> scopes = realm.getScope(user);
|
||||
System.out.println("Scopes size: " + scopes.size());
|
||||
Assert.assertTrue(scopes.contains("*"));
|
||||
Assert.assertEquals(0, realm.getSocialLinks(user).size());
|
||||
|
||||
List<ApplicationModel> resources = realm.getApplications();
|
||||
Assert.assertEquals(2, resources.size());
|
||||
List<RealmModel> realms = identitySession.getRealms(admin);
|
||||
|
@ -94,6 +100,28 @@ public class ImportTest {
|
|||
Assert.assertNotNull(oauthClient);
|
||||
Set<String> appScopes = application.getScope(oauthClient);
|
||||
Assert.assertTrue(appScopes.contains("user"));
|
||||
|
||||
// Test social linking
|
||||
UserModel socialUser = realm.getUser("mySocialUser");
|
||||
Set<SocialLinkModel> socialLinks = realm.getSocialLinks(socialUser);
|
||||
Assert.assertEquals(3, socialLinks.size());
|
||||
int facebookCount = 0;
|
||||
int googleCount = 0;
|
||||
for (SocialLinkModel socialLinkModel : socialLinks) {
|
||||
if ("facebook".equals(socialLinkModel.getSocialProvider())) {
|
||||
facebookCount++;
|
||||
} else if ("google".equals(socialLinkModel.getSocialProvider())) {
|
||||
googleCount++;
|
||||
Assert.assertEquals(socialLinkModel.getSocialUsername(), "mySocialUser@gmail.com");
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(2, facebookCount);
|
||||
Assert.assertEquals(1, googleCount);
|
||||
|
||||
UserModel foundSocialUser = realm.getUserBySocialLink(new SocialLinkModel("facebook", "fbuser1"));
|
||||
Assert.assertEquals(foundSocialUser.getLoginName(), socialUser.getLoginName());
|
||||
Assert.assertNull(realm.getUserBySocialLink(new SocialLinkModel("facebook", "not-existing")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -106,6 +134,7 @@ public class ImportTest {
|
|||
defaultRealm.setSslNotRequired(false);
|
||||
defaultRealm.setCookieLoginAllowed(true);
|
||||
defaultRealm.setRegistrationAllowed(true);
|
||||
defaultRealm.setAutomaticRegistrationAfterSocialLogin(false);
|
||||
manager.generateRealmKeys(defaultRealm);
|
||||
defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||
RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
|
||||
|
@ -117,6 +146,7 @@ public class ImportTest {
|
|||
manager.importRealm(rep, realm);
|
||||
realm.addRealmAdmin(admin);
|
||||
|
||||
Assert.assertTrue(realm.isAutomaticRegistrationAfterSocialLogin());
|
||||
verifyRequiredCredentials(realm.getRequiredCredentials(), "password");
|
||||
verifyRequiredCredentials(realm.getRequiredApplicationCredentials(), "totp");
|
||||
verifyRequiredCredentials(realm.getRequiredOAuthClientCredentials(), "cert");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"accessCodeLifespan": 10,
|
||||
"sslNotRequired": true,
|
||||
"cookieLoginAllowed": true,
|
||||
"automaticRegistrationAfterSocialLogin": true,
|
||||
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
|
||||
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||
"requiredCredentials": [ "password" ],
|
||||
|
|
|
@ -50,6 +50,10 @@
|
|||
"value": "clientpassword"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"username": "mySocialUser",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"roleMappings": [
|
||||
|
@ -64,6 +68,25 @@
|
|||
"roles": ["*"]
|
||||
}
|
||||
],
|
||||
"socialMappings": [
|
||||
{
|
||||
"username": "mySocialUser",
|
||||
"socialLinks": [
|
||||
{
|
||||
"socialProvider": "facebook",
|
||||
"socialUsername": "fbuser1"
|
||||
},
|
||||
{
|
||||
"socialProvider": "facebook",
|
||||
"socialUsername": "fbuser2"
|
||||
},
|
||||
{
|
||||
"socialProvider": "google",
|
||||
"socialUsername": "mySocialUser@gmail.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"applications": [
|
||||
{
|
||||
"name": "Application",
|
||||
|
|
|
@ -28,16 +28,16 @@ import java.util.Map;
|
|||
*/
|
||||
public class AuthCallback {
|
||||
|
||||
private Map<String, String> attributes;
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private Map<String, String[]> queryParams;
|
||||
|
||||
public AuthCallback(Map<String, String> attributes, Map<String, String[]> queryParams) {
|
||||
public AuthCallback(Map<String, Object> attributes, Map<String, String[]> queryParams) {
|
||||
this.attributes = attributes;
|
||||
this.queryParams = queryParams;
|
||||
}
|
||||
|
||||
public String getAttribute(String name) {
|
||||
public Object getAttribute(String name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,9 +33,9 @@ public class AuthRequest {
|
|||
|
||||
private URI authUri;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
AuthRequest(String id, URI authUri, Map<String, String> attributes) {
|
||||
AuthRequest(String id, URI authUri, Map<String, Object> attributes) {
|
||||
this.id = id;
|
||||
this.authUri = authUri;
|
||||
this.attributes = attributes;
|
||||
|
@ -49,7 +49,7 @@ public class AuthRequest {
|
|||
return authUri;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ public class AuthRequestBuilder {
|
|||
|
||||
private UriBuilder b;
|
||||
|
||||
private Map<String, String> attributes;
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private String id;
|
||||
|
||||
|
@ -44,7 +44,7 @@ public class AuthRequestBuilder {
|
|||
AuthRequestBuilder req = new AuthRequestBuilder();
|
||||
req.id = id;
|
||||
req.b = UriBuilder.fromUri(path);
|
||||
req.attributes = new HashMap<String, String>();
|
||||
req.attributes = new HashMap<String, Object>();
|
||||
return req;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ public class AuthRequestBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public AuthRequestBuilder setAttribute(String name, String value) {
|
||||
public AuthRequestBuilder setAttribute(String name, Object value) {
|
||||
attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -32,9 +32,9 @@ public class RequestDetails {
|
|||
|
||||
private Map<String, String> clientAttributes;
|
||||
|
||||
private Map<String, String> socialAttributes;
|
||||
private Map<String, Object> socialAttributes;
|
||||
|
||||
RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, String> socialAttributes) {
|
||||
RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, Object> socialAttributes) {
|
||||
this.providerId = providerId;
|
||||
this.clientAttributes = clientAttributes;
|
||||
this.socialAttributes = socialAttributes;
|
||||
|
@ -52,11 +52,11 @@ public class RequestDetails {
|
|||
return clientAttributes;
|
||||
}
|
||||
|
||||
public String getSocialAttribute(String name) {
|
||||
public Object getSocialAttribute(String name) {
|
||||
return socialAttributes.get(name);
|
||||
}
|
||||
|
||||
public Map<String, String> getSocialAttributes() {
|
||||
public Map<String, Object> getSocialAttributes() {
|
||||
return socialAttributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ public class RequestDetailsBuilder {
|
|||
|
||||
private Map<String, String> clientAttributes;
|
||||
|
||||
private Map<String, String> socialAttributes;
|
||||
private Map<String, Object> socialAttributes;
|
||||
|
||||
private RequestDetailsBuilder() {
|
||||
}
|
||||
|
@ -42,7 +42,17 @@ public class RequestDetailsBuilder {
|
|||
RequestDetailsBuilder req = new RequestDetailsBuilder();
|
||||
req.providerId = providerId;
|
||||
req.clientAttributes = new HashMap<String, String>();
|
||||
req.socialAttributes = new HashMap<String, String>();
|
||||
req.socialAttributes = new HashMap<String, Object>();
|
||||
return req;
|
||||
}
|
||||
|
||||
public static RequestDetailsBuilder createFromRequestDetails(RequestDetails from) {
|
||||
RequestDetailsBuilder req = new RequestDetailsBuilder();
|
||||
req.providerId = from.getProviderId();
|
||||
req.clientAttributes = new HashMap<String, String>();
|
||||
req.clientAttributes.putAll(from.getClientAttributes());
|
||||
req.socialAttributes = new HashMap<String, Object>();
|
||||
req.socialAttributes.putAll(from.getSocialAttributes());
|
||||
return req;
|
||||
}
|
||||
|
||||
|
@ -56,12 +66,12 @@ public class RequestDetailsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public RequestDetailsBuilder putSocialAttribute(String name, String value) {
|
||||
public RequestDetailsBuilder putSocialAttribute(String name, Object value) {
|
||||
socialAttributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RequestDetailsBuilder putSocialAttributes(Map<String, String> attributes) {
|
||||
public RequestDetailsBuilder putSocialAttributes(Map<String, Object> attributes) {
|
||||
socialAttributes.putAll(attributes);
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package org.keycloak.social;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SocialConstants {
|
||||
|
||||
public static final String ATTR_SOCIAL_LINK = "ATTR_SOCIAL_LINK";
|
||||
|
||||
public static final String SOCIAL_REGISTRATION_COOKIE = "SOCIAL_REGISTRATION_COOKIE";
|
||||
}
|
|
@ -57,6 +57,11 @@ public class SocialRequestManager {
|
|||
|
||||
return details;
|
||||
}
|
||||
|
||||
// Just obtain data without expiring it
|
||||
public synchronized RequestDetails getData(String requestId) {
|
||||
return map.get(requestId);
|
||||
}
|
||||
|
||||
private void pruneExpired() {
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.social;
|
|||
public class SocialUser {
|
||||
|
||||
private String id;
|
||||
private String username;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String email;
|
||||
|
@ -19,6 +20,14 @@ public class SocialUser {
|
|||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
|
|
@ -82,6 +82,13 @@ public class FacebookProvider implements SocialProvider {
|
|||
FacebookUser facebookUser = loadUser(accessToken, client);
|
||||
|
||||
SocialUser socialUser = new SocialUser(facebookUser.getId());
|
||||
socialUser.setUsername(facebookUser.getUsername());
|
||||
|
||||
// This could happen with Facebook testing users
|
||||
if (facebookUser.getUsername() == null || facebookUser.getUsername().length() == 0) {
|
||||
socialUser.setUsername(facebookUser.getId());
|
||||
}
|
||||
|
||||
socialUser.setEmail(facebookUser.getEmail());
|
||||
socialUser.setLastName(facebookUser.getLastName());
|
||||
socialUser.setFirstName(facebookUser.getFirstName());
|
||||
|
|
|
@ -106,6 +106,10 @@ public class GoogleProvider implements SocialProvider {
|
|||
Userinfo userInfo = oauth2.userinfo().get().execute();
|
||||
|
||||
SocialUser user = new SocialUser(userInfo.getId());
|
||||
|
||||
// Use email as username for Google
|
||||
user.setUsername(userInfo.getEmail());
|
||||
|
||||
user.setFirstName(userInfo.getGivenName());
|
||||
user.setLastName(userInfo.getFamilyName());
|
||||
user.setEmail(userInfo.getEmail());
|
||||
|
|
|
@ -71,13 +71,24 @@ public class TwitterProvider implements SocialProvider {
|
|||
twitter.setOAuthConsumer(config.getKey(), config.getSecret());
|
||||
|
||||
String verifier = callback.getQueryParam("oauth_verifier");
|
||||
RequestToken requestToken = new RequestToken(callback.getAttribute("token"), callback.getAttribute("tokenSecret"));
|
||||
RequestToken requestToken = new RequestToken((String)callback.getAttribute("token"), (String)callback.getAttribute("tokenSecret"));
|
||||
|
||||
twitter.getOAuthAccessToken(requestToken, verifier);
|
||||
twitter4j.User twitterUser = twitter.verifyCredentials();
|
||||
|
||||
SocialUser user = new SocialUser(Long.toString(twitterUser.getId()));
|
||||
user.setFirstName(twitterUser.getName());
|
||||
|
||||
// Use screenName as username for Twitter
|
||||
user.setUsername(twitterUser.getScreenName());
|
||||
|
||||
String twitterName = twitterUser.getName();
|
||||
int spaceIndex = twitterName.lastIndexOf(' ');
|
||||
if (spaceIndex != -1) {
|
||||
user.setFirstName(twitterName.substring(0, spaceIndex));
|
||||
user.setLastName(twitterName.substring(spaceIndex + 1));
|
||||
} else {
|
||||
user.setFirstName(twitterName);
|
||||
}
|
||||
|
||||
return user;
|
||||
} catch (Exception e) {
|
||||
|
|
Loading…
Reference in a new issue