Merge pull request #290 from patriot1burke/master

buncha stuff
This commit is contained in:
Bill Burke 2014-03-12 15:58:22 -04:00
commit 6926f02cc3
19 changed files with 205 additions and 18 deletions

View file

@ -37,7 +37,7 @@
<tr>
<th class="kc-table-actions" colspan="3">
<div class="pull-right">
<a class="btn btn-primary" ng-click="logoutAll()">Logout All</a>
<a class="btn btn-primary" ng-click="logoutAll()">Invalidate All Sessions</a>
<a class="btn btn-primary" ng-click="loadUsers()">Show Users</a>
</div>
</th>
@ -52,7 +52,7 @@
<tr data-ng-repeat="(user, data) in users">
<td><a href="#/realms/{{realm.realm}}/users/{{user}}">{{user}}</a></td>
<td>{{data.whenLoggedIn | date:'medium'}}</td>
<td><a ng-click="logoutUser(user)">logout</a> </td>
<td><a ng-click="logoutUser(user)">invalidate session</a> </td>
</tr>
</tbody>
</table>

View file

@ -33,7 +33,7 @@
<tr data-ng-repeat="(application, data) in stats">
<td><a href="#/realms/{{realm.realm}}/applications/{{application}}/sessions">{{application}}</a></td>
<td>{{data.whenLoggedIn | date:'medium'}}</td>
<td><a ng-click="logoutApplication(application)">logout</a> </td>
<td><a ng-click="logoutApplication(application)">invalidate session</a> </td>
</tr>
</tbody>
</table>

View file

@ -7,11 +7,12 @@ package org.keycloak.representations.adapters.action;
public class LogoutAction extends AdminAction {
public static final String LOGOUT = "LOGOUT";
protected String user;
protected int notBefore;
public LogoutAction() {
}
public LogoutAction(String id, int expiration, String resource, String user) {
public LogoutAction(String id, int expiration, String resource, String user, int notBefore) {
super(id, expiration, resource, LOGOUT);
this.user = user;
}
@ -24,6 +25,14 @@ public class LogoutAction extends AdminAction {
this.user = user;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.notBefore = notBefore;
}
@Override
public boolean validate() {
return LOGOUT.equals(action);

View file

@ -15,9 +15,9 @@
{
"username" : "bburke@redhat.com",
"enabled": true,
"attributes" : {
"email" : "bburke@redhat.com"
},
"email" : "bburke@redhat.com",
"firstName": "Bill",
"lastName": "Burke",
"credentials" : [
{ "type" : "password",
"value" : "password" }

View file

@ -105,6 +105,9 @@ public class PreAuthActionsHandler {
userSessionManagement.logout(user);
} else {
log.info("logout of all sessions");
if (action.getNotBefore() > deployment.getNotBefore()) {
deployment.setNotBefore(action.getNotBefore());
}
userSessionManagement.logoutAll();
}
} catch (Exception e) {

View file

@ -55,6 +55,9 @@ public interface UserModel {
void setTotp(boolean totp);
int getNotBefore();
void setNotBefore(int notBefore);
public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD
}

View file

@ -83,7 +83,9 @@ public class JpaKeycloakSession implements KeycloakSession {
adapter.removeApplication(a.getId());
}
em.createQuery("delete from " + OAuthClientEntity.class.getSimpleName() + " where realm = :realm").setParameter("realm", realm).executeUpdate();
for (OAuthClientModel oauth : adapter.getOAuthClients()) {
adapter.removeOAuthClient(oauth.getId());
}
for (UserEntity u : em.createQuery("from UserEntity", UserEntity.class).getResultList()) {
adapter.removeUser(u.getLoginName());

View file

@ -144,4 +144,14 @@ public class UserAdapter implements UserModel {
public void setTotp(boolean totp) {
user.setTotp(totp);
}
@Override
public int getNotBefore() {
return user.getNotBefore();
}
@Override
public void setNotBefore(int notBefore) {
user.setNotBefore(notBefore);
}
}

View file

@ -46,6 +46,7 @@ public class UserEntity {
protected boolean enabled;
protected boolean totp;
protected boolean emailVerified;
protected int notBefore;
@ManyToOne
protected RealmEntity realm;
@ -158,4 +159,12 @@ public class UserEntity {
public void setCredentials(Collection<CredentialEntity> credentials) {
this.credentials = credentials;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.notBefore = notBefore;
}
}

View file

@ -45,6 +45,16 @@ public class UserAdapter extends AbstractMongoAdapter<UserEntity> implements Use
updateUser();
}
@Override
public int getNotBefore() {
return user.getNotBefore();
}
@Override
public void setNotBefore(int notBefore) {
user.setNotBefore(notBefore);
}
@Override
public String getFirstName() {
return user.getFirstName();

View file

@ -23,6 +23,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
private boolean emailVerified;
private boolean totp;
private boolean enabled;
private int notBefore;
private String realmId;
@ -96,6 +97,15 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
this.totp = totp;
}
@MongoField
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.notBefore = notBefore;
}
@MongoField
public String getRealmId() {
return realmId;

View file

@ -28,6 +28,16 @@ import java.util.Set;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ImportTest extends AbstractModelTest {
@Test
public void demoDelete() throws Exception {
// was having trouble deleting this realm from admin console
RealmRepresentation rep = AbstractModelTest.loadJson("testrealm2.json");
RealmModel realm = realmManager.importRealm(rep);
commit();
realm = realmManager.getRealmByName("demo-delete");
realmManager.removeRealm(realm);
}
@Test
public void install() throws Exception {
RealmRepresentation rep = AbstractModelTest.loadJson("testrealm.json");
@ -160,6 +170,13 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(realm.removeSocialLink(socialUser, "facebook"));
Assert.assertNull(realm.getSocialLink(socialUser, "facebook"));
Assert.assertFalse(realm.removeSocialLink(socialUser, "facebook"));
commit();
realm = realmManager.getRealm("demo");
realmManager.removeRealm(realm);
}
@Test

View file

@ -0,0 +1,100 @@
{
"realm": "demo-delete",
"enabled": true,
"accessTokenLifespan": 3000,
"accessCodeLifespan": 10,
"accessCodeLifespanUserAction": 6000,
"sslNotRequired": true,
"registrationAllowed": false,
"social": false,
"updateProfileOnInitialSocialLogin": 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" ],
"users" : [
{
"username" : "bburke@redhat.com",
"enabled": true,
"email" : "bburke@redhat.com",
"firstName": "Bill",
"lastName": "Burke",
"credentials" : [
{ "type" : "password",
"value" : "password" }
]
}
],
"roles" : {
"realm" : [
{
"name": "user",
"description": "User privileges"
},
{
"name": "admin",
"description": "Administrator privileges"
}
]
},
"roleMappings": [
{
"username": "bburke@redhat.com",
"roles": ["user"]
}
],
"scopeMappings": [
{
"client": "third-party",
"roles": ["user"]
},
{
"client": "customer-portal",
"roles": ["user"]
},
{
"client": "product-portal",
"roles": ["user"]
}
],
"applications": [
{
"name": "customer-portal",
"enabled": true,
"adminUrl": "http://localhost:8080/customer-portal",
"redirectUris": [
"http://localhost:8080/customer-portal/*"
],
"secret": "password"
},
{
"name": "product-portal",
"enabled": true,
"adminUrl": "http://localhost:8080/product-portal",
"redirectUris": [
"http://localhost:8080/product-portal/*"
],
"secret": "password"
}
],
"oauthClients": [
{
"name": "third-party",
"enabled": true,
"redirectUris": [
"http://localhost:8080/oauth-client/*",
"http://localhost:8080/oauth-client-cdi/*"
],
"secret": "password"
}
],
"applicationRoleMappings": {
"account": [
{
"username": "bburke@redhat.com",
"roles": ["manage-account"]
}
]
}
}

View file

@ -156,12 +156,19 @@ public class AuthenticationManager {
}
UserModel user = realm.getUserById(token.getSubject());
if (user == null || !user.isEnabled()) {
if (user == null || !user.isEnabled() ) {
logger.info("Unknown user in identity cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
if (token.getIssuedAt() < user.getNotBefore()) {
logger.info("Stale cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
return user;
} catch (VerificationException e) {
logger.info("Failed to verify identity cookie", e);

View file

@ -423,6 +423,8 @@ public class RealmManager {
UserModel user = newRealm.addUser(userRep.getUsername());
user.setEnabled(userRep.isEnabled());
user.setEmail(userRep.getEmail());
user.setFirstName(userRep.getFirstName());
user.setLastName(userRep.getLastName());
if (userRep.getAttributes() != null) {
for (Map.Entry<String, String> entry : userRep.getAttributes().entrySet()) {
user.setAttribute(entry.getKey(), entry.getValue());

View file

@ -108,16 +108,17 @@ public class ResourceAdminManager {
}
public void logoutUser(RealmModel realm, String user) {
public void logoutUser(RealmModel realm, UserModel user) {
ResteasyClient client = new ResteasyClientBuilder()
.disableTrustManager() // todo fix this, should have a trust manager or a good default
.build();
try {
// don't set user notBefore as we don't want a database hit on a user driven logout
List<ApplicationModel> resources = realm.getApplications();
logger.debug("logging out {0} resources ", resources.size());
for (ApplicationModel resource : resources) {
logoutApplication(realm, resource, user, client);
logoutApplication(realm, resource, user.getId(), client, 0);
}
} finally {
client.close();
@ -129,10 +130,11 @@ public class ResourceAdminManager {
.build();
try {
realm.setNotBefore((int)(System.currentTimeMillis()/1000));
List<ApplicationModel> resources = realm.getApplications();
logger.debug("logging out {0} resources ", resources.size());
for (ApplicationModel resource : resources) {
logoutApplication(realm, resource, null, client);
logoutApplication(realm, resource, null, client, realm.getNotBefore());
}
} finally {
client.close();
@ -145,7 +147,8 @@ public class ResourceAdminManager {
.build();
try {
logoutApplication(realm, resource, user, client);
resource.setNotBefore((int)(System.currentTimeMillis()/1000));
logoutApplication(realm, resource, user, client, resource.getNotBefore());
} finally {
client.close();
}
@ -153,10 +156,10 @@ public class ResourceAdminManager {
}
protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client) {
protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ResteasyClient client, int notBefore) {
String managementUrl = resource.getManagementUrl();
if (managementUrl != null) {
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user);
LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), (int)(System.currentTimeMillis() / 1000) + 30, resource.getName(), user, notBefore);
String token = new TokenManager().encodeToken(realm, adminAction);
logger.info("logout user: {0} resource: {1} url: {2}", user, resource.getName(), managementUrl);
Response response = client.target(managementUrl).path(AdapterConstants.K_LOGOUT).request().post(Entity.text(token));

View file

@ -131,7 +131,7 @@ public class TokenManager {
}
if (refreshToken.getIssuedAt() < client.getNotBefore()) {
if (refreshToken.getIssuedAt() < client.getNotBefore() || refreshToken.getIssuedAt() < user.getNotBefore()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
}

View file

@ -580,7 +580,7 @@ public class TokenService {
logger.info("Logging out: {0}", user.getLoginName());
authManager.expireIdentityCookie(realm, uriInfo);
authManager.expireRememberMeCookie(realm, uriInfo);
resourceAdminManager.logoutUser(realm, user.getId());
resourceAdminManager.logoutUser(realm, user);
} else {
logger.info("No user logged in for logout");
}

View file

@ -176,7 +176,9 @@ public class UsersResource {
if (user == null) {
throw new NotFoundException();
}
new ResourceAdminManager().logoutUser(realm, user.getId());
// set notBefore so that user will be forced to log in.
user.setNotBefore((int)(System.currentTimeMillis()/1000));
new ResourceAdminManager().logoutUser(realm, user);
}