Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2014-05-08 20:03:20 -04:00
commit d957cc4883
17 changed files with 139 additions and 7 deletions

View file

@ -21,6 +21,7 @@ import org.keycloak.social.SocialProvider;
public class AccountSocialBean {
private final List<SocialLinkEntry> socialLinks;
private final boolean removeLinkPossible;
public AccountSocialBean(RealmModel realm, UserModel user, URI baseUri) {
URI accountSocialUpdateUri = Urls.accountSocialUpdate(baseUri, realm.getName());
@ -29,12 +30,16 @@ public class AccountSocialBean {
Map<String, String> socialConfig = realm.getSocialConfig();
Set<SocialLinkModel> userSocialLinks = realm.getSocialLinks(user);
int availableLinks = 0;
if (socialConfig != null && !socialConfig.isEmpty()) {
for (SocialProvider provider : SocialLoader.load()) {
String socialProviderId = provider.getId();
if (socialConfig.containsKey(socialProviderId + ".key")) {
SocialLinkModel socialLink = getSocialLink(userSocialLinks, socialProviderId);
if (socialLink != null) {
availableLinks++;
}
String action = socialLink != null ? "remove" : "add";
String actionUrl = UriBuilder.fromUri(accountSocialUpdateUri).queryParam("action", action).queryParam("provider_id", socialProviderId).build().toString();
@ -43,6 +48,9 @@ public class AccountSocialBean {
}
}
}
// Removing last social provider is not possible if you don't have other possibility to authenticate
this.removeLinkPossible = availableLinks > 1 || realm.getAuthenticationLink(user) != null;
}
private SocialLinkModel getSocialLink(Set<SocialLinkModel> userSocialLinks, String socialProviderId) {
@ -58,6 +66,10 @@ public class AccountSocialBean {
return socialLinks;
}
public boolean isRemoveLinkPossible() {
return removeLinkPossible;
}
public class SocialLinkEntry {
private SocialLinkModel link;

View file

@ -30,6 +30,7 @@ missingSocialProvider=Social provider not specified
invalidSocialAction=Invalid or missing action
socialProviderNotFound=Specified social provider not found
socialLinkNotActive=This social link is not active anymore
socialRemovingLastProvider=You can't remove last social provider as you don't have password
socialRedirectError=Failed to redirect to social provider
socialProviderRemoved=Social provider removed successfully

View file

@ -18,7 +18,9 @@
</div>
<div class="col-sm-5 col-md-5">
<#if socialLink.connected>
<a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Remove ${socialLink.providerName!}</a>
<#if social.removeLinkPossible>
<a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Remove ${socialLink.providerName!}</a>
</#if>
<#else>
<a href="${socialLink.actionUrl}" type="submit" class="btn btn-primary btn-lg">Add ${socialLink.providerName!}</a>
</#if>

View file

@ -300,6 +300,21 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'UserSessionsCtrl'
})
.when('/realms/:realm/users/:user/social-links', {
templateUrl : 'partials/user-social-links.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
user : function(UserLoader) {
return UserLoader();
},
socialLinks : function(UserSocialLinksLoader) {
return UserSocialLinksLoader();
}
},
controller : 'UserSocialCtrl'
})
.when('/realms/:realm/users', {
templateUrl : 'partials/user-list.html',
resolve : {

View file

@ -146,6 +146,13 @@ module.controller('UserSessionsCtrl', function($scope, realm, user, stats, UserL
});
module.controller('UserSocialCtrl', function($scope, realm, user, socialLinks) {
$scope.realm = realm;
$scope.user = user;
$scope.socialLinks = socialLinks;
console.log('showing social links of user');
});
module.controller('UserListCtrl', function($scope, realm, User) {
$scope.realm = realm;

View file

@ -89,6 +89,15 @@ module.factory('UserSessionStatsLoader', function(Loader, UserSessionStats, $rou
});
});
module.factory('UserSocialLinksLoader', function(Loader, UserSocialLinks, $route, $q) {
return Loader.query(UserSocialLinks, function() {
return {
realm : $route.current.params.realm,
user : $route.current.params.user
}
});
});
module.factory('RoleLoader', function(Loader, Role, $route, $q) {
return Loader.get(Role, function() {
return {

View file

@ -189,6 +189,12 @@ module.factory('UserLogout', function($resource) {
user : '@user'
});
});
module.factory('UserSocialLinks', function($resource) {
return $resource(authUrl + '/rest/admin/realms/:realm/users/:user/social-links', {
realm : '@realm',
user : '@user'
});
});
module.factory('UserCredentials', function($resource) {
var credentials = {};

View file

@ -5,6 +5,7 @@
<li data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
</ul>
<div id="content">
<ol class="breadcrumb">

View file

@ -6,6 +6,7 @@
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
</ul>
<div id="content">

View file

@ -6,6 +6,7 @@
<li data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
</ul>
<div id="content">

View file

@ -5,6 +5,7 @@
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
</ul>
<div id="content">
<ol class="breadcrumb">

View file

@ -0,0 +1,33 @@
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Social Links</a></li>
</ul>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}">{{realm.realm}}</a></li>
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">{{user.username}}</a></li>
<li class="active">Social Links</li>
</ol>
<h2><span>{{user.username}}</span> Social Links</h2>
<table class="table">
<thead>
<tr>
<th>Provider Name</th>
<th>Social Username</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="socialLink in socialLinks">
<td>{{socialLink.socialProvider}}</td>
<td>{{socialLink.socialUsername}}</td>
</tr>
</tbody>
</table>
</div>
</div>

View file

@ -8,6 +8,7 @@ import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
@ -16,6 +17,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmAuditRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.ArrayList;
@ -168,4 +170,12 @@ public class ModelToRepresentation {
rep.setUsername(ClaimMask.hasUsername(model.getAllowedClaimsMask()));
return rep;
}
public static SocialLinkRepresentation toRepresentation(SocialLinkModel socialLink) {
SocialLinkRepresentation rep = new SocialLinkRepresentation();
rep.setSocialUsername(socialLink.getSocialUsername());
rep.setSocialProvider(socialLink.getSocialProvider());
rep.setSocialUserId(socialLink.getSocialUserId());
return rep;
}
}

View file

@ -71,6 +71,8 @@ public class Messages {
public static final String SOCIAL_LINK_NOT_ACTIVE = "socialLinkNotActive";
public static final String SOCIAL_REMOVING_LAST_PROVIDER = "socialRemovingLastProvider";
public static final String SOCIAL_REDIRECT_ERROR = "socialRedirectError";
public static final String SOCIAL_PROVIDER_REMOVED = "socialProviderRemoved";

View file

@ -423,15 +423,21 @@ public class AccountService {
case REMOVE:
SocialLinkModel link = realm.getSocialLink(user, providerId);
if (link != null) {
realm.removeSocialLink(user, providerId);
logger.debug("Social provider " + providerId + " removed successfully from user " + user.getLoginName());
// Removing last social provider is not possible if you don't have other possibility to authenticate
if (realm.getSocialLinks(user).size() > 1 || realm.getAuthenticationLink(user) != null) {
realm.removeSocialLink(user, providerId);
audit.event(Events.REMOVE_SOCIAL_LINK).client(auth.getClient()).user(auth.getUser())
.detail(Details.USERNAME, link.getSocialUserId() + "@" + link.getSocialProvider())
.success();
logger.debug("Social provider " + providerId + " removed successfully from user " + user.getLoginName());
return account.setSuccess(Messages.SOCIAL_PROVIDER_REMOVED).createResponse(AccountPages.SOCIAL);
audit.event(Events.REMOVE_SOCIAL_LINK).client(auth.getClient()).user(auth.getUser())
.detail(Details.USERNAME, link.getSocialUserId() + "@" + link.getSocialProvider())
.success();
return account.setSuccess(Messages.SOCIAL_PROVIDER_REMOVED).createResponse(AccountPages.SOCIAL);
} else {
return account.setError(Messages.SOCIAL_REMOVING_LAST_PROVIDER).createResponse(AccountPages.SOCIAL);
}
} else {
return account.setError(Messages.SOCIAL_LINK_NOT_ACTIVE).createResponse(AccountPages.SOCIAL);
}

View file

@ -11,6 +11,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.adapters.action.UserStats;
@ -18,6 +19,7 @@ import org.keycloak.representations.idm.ApplicationMappingsRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.email.EmailException;
import org.keycloak.services.email.EmailSender;
@ -179,6 +181,25 @@ public class UsersResource {
return stats;
}
@Path("{username}/social-links")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<SocialLinkRepresentation> getSocialLinks(final @PathParam("username") String username) {
auth.requireView();
UserModel user = realm.getUser(username);
if (user == null) {
throw new NotFoundException("User not found");
}
Set<SocialLinkModel> socialLinks = realm.getSocialLinks(user);
List<SocialLinkRepresentation> result = new ArrayList<SocialLinkRepresentation>();
for (SocialLinkModel socialLink : socialLinks) {
SocialLinkRepresentation rep = ModelToRepresentation.toRepresentation(socialLink);
result.add(rep);
}
return result;
}
@Path("{username}/logout")
@POST
public void logout(final @PathParam("username") String username) {

View file

@ -173,6 +173,10 @@
<artifactId>keycloak-social-twitter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.twitter4j</groupId>
<artifactId>twitter4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-facebook</artifactId>