Skip to content
Permalink
Browse files
Implemented application/permittablegroup/user/enabled endpoint so tha…
…t users can themselves give and remove permissions to applications to act in their name.
  • Loading branch information
mifosio-04-04-2018 committed Apr 15, 2017
1 parent 9e3d2d4 commit cc5495059d14c95fedc2d9e7ec47620c587e21f5
Showing 14 changed files with 597 additions and 128 deletions.
@@ -128,6 +128,19 @@ public interface IdentityManager extends Anubis {
produces = {MediaType.ALL_VALUE})
List<String> getApplications();

@RequestMapping(value = "/applications/{applicationidentifier}/signatures/{timestamp}", method = RequestMethod.PUT,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
void setApplicationSignature(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("timestamp") @ValidKeyTimestamp String timestamp,
Signature signature);

@RequestMapping(value = "/applications/{applicationidentifier}/signatures/{timestamp}", method = RequestMethod.GET,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
Signature getApplicationSignature(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("timestamp") @ValidKeyTimestamp String timestamp);

@RequestMapping(value = "/applications/{applicationidentifier}", method = RequestMethod.DELETE,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
@@ -149,32 +162,20 @@ public interface IdentityManager extends Anubis {
void deleteApplicationPermission(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("permissionidentifier") String permittableEndpointGroupIdentifier);

@RequestMapping(value = "/applications/{applicationidentifier}/permissions/{permissionidentifier}/approvals/{useridentifier}", method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
void createApplicationPermissionUserApproval(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("permissionidentifier") String permittableEndpointGroupIdentifier,
@PathVariable("useridentifier") String userIdentifier);

@RequestMapping(value = "/applications/{applicationidentifier}/permissions/{permissionidentifier}/approvals/{useridentifier}", method = RequestMethod.DELETE,
@RequestMapping(value = "/applications/{applicationidentifier}/permissions/{permissionidentifier}/users/{useridentifier}/enabled", method = RequestMethod.PUT,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
void deleteApplicationPermissionUserApproval(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("permissionidentifier") String permittableEndpointGroupIdentifier,
@PathVariable("useridentifier") String userIdentifier);

@RequestMapping(value = "/applications/{applicationidentifier}/signatures/{timestamp}", method = RequestMethod.PUT,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
void setApplicationSignature(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("timestamp") @ValidKeyTimestamp String timestamp,
Signature signature);
void setApplicationPermissionEnabledForUser(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("permissionidentifier") String permittableEndpointGroupIdentifier,
@PathVariable("useridentifier") String userIdentifier,
Boolean enabled);

@RequestMapping(value = "/applications/{applicationidentifier}/signatures/{timestamp}", method = RequestMethod.GET,
@RequestMapping(value = "/applications/{applicationidentifier}/permissions/{permissionidentifier}/users/{useridentifier}/enabled", method = RequestMethod.GET,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.ALL_VALUE})
Signature getApplicationSignature(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("timestamp") @ValidKeyTimestamp String timestamp);
produces = {MediaType.APPLICATION_JSON_VALUE})
boolean getApplicationPermissionEnabledForUser(@PathVariable("applicationidentifier") String applicationIdentifier,
@PathVariable("permissionidentifier") String permittableEndpointGroupIdentifier,
@PathVariable("useridentifier") String userIdentifier);

@RequestMapping(value = "/initialize", method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE},
@@ -0,0 +1,85 @@
/*
* Copyright 2017 The Mifos Initiative.
*
* 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 io.mifos.identity.api.v1.events;

import java.util.Objects;

/**
* @author Myrle Krantz
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public class ApplicationPermissionUserEvent {
private String applicationIdentifier;
private String permittableGroupIdentifier;
private String userIdentifier;

public ApplicationPermissionUserEvent() {
}

public ApplicationPermissionUserEvent(String applicationIdentifier, String permittableGroupIdentifier, String userIdentifier) {
this.applicationIdentifier = applicationIdentifier;
this.permittableGroupIdentifier = permittableGroupIdentifier;
this.userIdentifier = userIdentifier;
}

public String getApplicationIdentifier() {
return applicationIdentifier;
}

public void setApplicationIdentifier(String applicationIdentifier) {
this.applicationIdentifier = applicationIdentifier;
}

public String getPermittableGroupIdentifier() {
return permittableGroupIdentifier;
}

public void setPermittableGroupIdentifier(String permittableGroupIdentifier) {
this.permittableGroupIdentifier = permittableGroupIdentifier;
}

public String getUserIdentifier() {
return userIdentifier;
}

public void setUserIdentifier(String userIdentifier) {
this.userIdentifier = userIdentifier;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ApplicationPermissionUserEvent that = (ApplicationPermissionUserEvent) o;
return Objects.equals(applicationIdentifier, that.applicationIdentifier) &&
Objects.equals(permittableGroupIdentifier, that.permittableGroupIdentifier) &&
Objects.equals(userIdentifier, that.userIdentifier);
}

@Override
public int hashCode() {
return Objects.hash(applicationIdentifier, permittableGroupIdentifier, userIdentifier);
}

@Override
public String toString() {
return "ApplicationPermissionUserEvent{" +
"applicationIdentifier='" + applicationIdentifier + '\'' +
", permittableGroupIdentifier='" + permittableGroupIdentifier + '\'' +
", userIdentifier='" + userIdentifier + '\'' +
'}';
}
}
@@ -36,10 +36,11 @@ public interface EventConstants {
String OPERATION_PUT_USER_ROLEIDENTIFIER = "put-user-roleidentifier";
String OPERATION_PUT_USER_PASSWORD = "put-user-password";

String OPERATION_PUT_APPLICATION_SIGNATURE = "put-application-signature";
String OPERATION_DELETE_APPLICATION = "delete-application";
String OPERATION_POST_APPLICATION_PERMISSION = "post-application-permission";
String OPERATION_DELETE_APPLICATION_PERMISSION = "delete-application-permission";
String OPERATION_PUT_APPLICATION_SIGNATURE = "put-application-signature";
String OPERATION_PUT_APPLICATION_PERMISSION_USER_ENABLED = "put-application-permission-user-enabled";

String SELECTOR_AUTHENTICATE = OPERATION_HEADER + " = '" + OPERATION_AUTHENTICATE + "'";

@@ -53,8 +54,9 @@ public interface EventConstants {
String SELECTOR_PUT_USER_ROLEIDENTIFIER = OPERATION_HEADER + " = '" + OPERATION_PUT_USER_ROLEIDENTIFIER + "'";
String SELECTOR_PUT_USER_PASSWORD = OPERATION_HEADER + " = '" + OPERATION_PUT_USER_PASSWORD + "'";

String SELECTOR_PUT_APPLICATION_SIGNATURE = OPERATION_HEADER + " = '" + OPERATION_PUT_APPLICATION_SIGNATURE + "'";
String SELECTOR_DELETE_APPLICATION = OPERATION_HEADER + " = '" + OPERATION_DELETE_APPLICATION + "'";
String SELECTOR_POST_APPLICATION_PERMISSION = OPERATION_HEADER + " = '" + OPERATION_POST_APPLICATION_PERMISSION + "'";
String SELECTOR_DELETE_APPLICATION_PERMISSION = OPERATION_HEADER + " = '" + OPERATION_DELETE_APPLICATION_PERMISSION + "'";
String SELECTOR_PUT_APPLICATION_SIGNATURE = OPERATION_HEADER + " = '" + OPERATION_PUT_APPLICATION_SIGNATURE + "'";
String SELECTOR_PUT_APPLICATION_PERMISSION_USER_ENABLED = OPERATION_HEADER + " = '" + OPERATION_PUT_APPLICATION_PERMISSION_USER_ENABLED + "'";
}
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import io.mifos.anubis.api.v1.domain.AllowedOperation;
import io.mifos.anubis.test.v1.TenantApplicationSecurityEnvironmentTestRule;
import io.mifos.core.api.config.EnableApiFactory;
import io.mifos.core.api.context.AutoUserContext;
@@ -23,11 +24,10 @@
import io.mifos.core.test.fixture.cassandra.CassandraInitializer;
import io.mifos.core.test.listener.EnableEventRecording;
import io.mifos.core.test.listener.EventRecorder;
import io.mifos.identity.api.v1.PermittableGroupIds;
import io.mifos.identity.api.v1.domain.*;
import io.mifos.identity.api.v1.events.EventConstants;
import io.mifos.identity.api.v1.client.IdentityManager;
import io.mifos.identity.api.v1.domain.Authentication;
import io.mifos.identity.api.v1.domain.Password;
import io.mifos.identity.api.v1.domain.UserWithPassword;
import io.mifos.identity.config.IdentityServiceConfig;
import org.junit.After;
import org.junit.Assert;
@@ -44,6 +44,7 @@
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.PostConstruct;
import java.util.Collections;

/**
* @author Myrle Krantz
@@ -184,4 +185,66 @@ String createUserWithNonexpiredPassword(final String password, final String role
}
return username;
}

String generateRoleIdentifier() {
return Helpers.generateRandomIdentifier("scribe");
}

Role buildRole(final String identifier, final Permission rolePermission) {
final Role scribe = new Role();
scribe.setIdentifier(identifier);
scribe.setPermissions(Collections.emptyList());
scribe.setPermissions(Collections.singletonList(rolePermission));
return scribe;
}

Permission buildRolePermission() {
final Permission permission = new Permission();
permission.setAllowedOperations(AllowedOperation.ALL);
permission.setPermittableEndpointGroupIdentifier(PermittableGroupIds.ROLE_MANAGEMENT);
return permission;
}

Permission buildUserPermission() {
final Permission permission = new Permission();
permission.setAllowedOperations(AllowedOperation.ALL);
permission.setPermittableEndpointGroupIdentifier(PermittableGroupIds.IDENTITY_MANAGEMENT);
return permission;
}

private Permission buildSelfPermission() {
final Permission permission = new Permission();
permission.setAllowedOperations(AllowedOperation.ALL);
permission.setPermittableEndpointGroupIdentifier(PermittableGroupIds.SELF_MANAGEMENT);
return permission;
}

String createRoleManagementRole() throws InterruptedException {
final String roleIdentifier = generateRoleIdentifier();
final Permission rolePermission = buildRolePermission();
final Role scribe = buildRole(roleIdentifier, rolePermission);

getTestSubject().createRole(scribe);

eventRecorder.wait(EventConstants.OPERATION_POST_ROLE, scribe.getIdentifier());

return roleIdentifier;
}

String createSelfManagementRole() throws InterruptedException {
final String roleIdentifier = generateRoleIdentifier();
final Permission rolePermission = buildSelfPermission();
final Role scribe = buildRole(roleIdentifier, rolePermission);

getTestSubject().createRole(scribe);

eventRecorder.wait(EventConstants.OPERATION_POST_ROLE, scribe.getIdentifier());

return roleIdentifier;
}

AutoUserContext loginUser(final String userId, final String password) {
final Authentication authentication = getTestSubject().login(userId, Helpers.encodePassword(password));
return new AutoUserContext(userId, authentication.getAccessToken());
}
}
@@ -22,6 +22,7 @@
import io.mifos.identity.api.v1.PermittableGroupIds;
import io.mifos.identity.api.v1.domain.Permission;
import io.mifos.identity.api.v1.events.ApplicationPermissionEvent;
import io.mifos.identity.api.v1.events.ApplicationPermissionUserEvent;
import io.mifos.identity.api.v1.events.ApplicationSignatureEvent;
import io.mifos.identity.api.v1.events.EventConstants;
import org.apache.commons.lang.RandomStringUtils;
@@ -124,6 +125,90 @@ public void testDeleteApplication() throws InterruptedException {
}
}

@Test
public void testApplicationApprovals() throws InterruptedException {
final ApplicationSignatureEvent appPlusSig;
final Permission identityManagementPermission;
try (final AutoUserContext ignored
= tenantApplicationSecurityEnvironment.createAutoSeshatContext()) {
appPlusSig = setApplicationSignature();

identityManagementPermission = new Permission(
PermittableGroupIds.ROLE_MANAGEMENT,
Collections.singleton(AllowedOperation.READ));

getTestSubject().createApplicationPermission(appPlusSig.getApplicationIdentifier(), identityManagementPermission);
Assert.assertTrue(eventRecorder.wait(EventConstants.OPERATION_POST_APPLICATION_PERMISSION,
new ApplicationPermissionEvent(appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier())));
}

final String user1Password;
final String user1id;
final String user2Password;
final String user2id;
try (final AutoUserContext ignored = enableAndLoginAdmin()) {
final String selfManagementRoleId = createSelfManagementRole();
final String roleManagementRoleId = createRoleManagementRole();

user1Password = RandomStringUtils.randomAlphanumeric(5);
user1id = createUserWithNonexpiredPassword(user1Password, selfManagementRoleId);

user2Password = RandomStringUtils.randomAlphanumeric(5);
user2id = createUserWithNonexpiredPassword(user2Password, roleManagementRoleId);
}

try (final AutoUserContext ignored = loginUser(user1id, user1Password)) {
Assert.assertFalse(getTestSubject().getApplicationPermissionEnabledForUser(
appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier(),
user1id));

getTestSubject().setApplicationPermissionEnabledForUser(
appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier(),
user1id,
true);

Assert.assertTrue(eventRecorder.wait(EventConstants.OPERATION_PUT_APPLICATION_PERMISSION_USER_ENABLED,
new ApplicationPermissionUserEvent(
appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier(),
user1id)));

Assert.assertTrue(getTestSubject().getApplicationPermissionEnabledForUser(
appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier(),
user1id));
}

try (final AutoUserContext ignored = loginUser(user2id, user2Password)) {
Assert.assertFalse(getTestSubject().getApplicationPermissionEnabledForUser(
appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier(),
user2id));
}

try (final AutoUserContext ignored = loginUser(user1id, user1Password)) {
getTestSubject().setApplicationPermissionEnabledForUser(
appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier(),
user1id,
false);

Assert.assertTrue(eventRecorder.wait(EventConstants.OPERATION_PUT_APPLICATION_PERMISSION_USER_ENABLED,
new ApplicationPermissionUserEvent(
appPlusSig.getApplicationIdentifier(),
identityManagementPermission.getPermittableEndpointGroupIdentifier(),
user1id)));
}

//Note that at this point, our imaginary application still cannot do anything in the name of any user,
//because neither of the users has the permission it enabled for the application.

//TODO: check that the permissions actually work when accessing endpoints as an application.
}

private ApplicationSignatureEvent setApplicationSignature() throws InterruptedException {
final String testApplicationName = createTestApplicationName();
final RsaKeyPairFactory.KeyPairHolder keyPair = RsaKeyPairFactory.createKeyPair();

0 comments on commit cc54950

Please sign in to comment.