Skip to content

Commit

Permalink
Feature/acl (#131)
Browse files Browse the repository at this point in the history
* SQL scripts updated for new tables/types

* ACL Model and Enum classes

* Initial model/enum classes filled out

* Notes on relationships

* ACL Repository classes created

* Update to new model

* Removed no longer needed fields

* ACL Entity will have unique name field constraint

* Additional updates for new model

* ACL data relationships expressed

* AclEntityService created

* AclUserPermission rename (removed s)

* Acl Repositories passed correct type

* AclEntity testCreate success

* CreateUniqueName test success

* Get and Update methods w/ test success

* Additonal get methods/test and partial delete

* Delete with passing test

* AclEntity List tests

* Formatting

* Permissions as a single base with two implementers

* Permission base class complete

* ACL Group/User permission classes

* UserService starting to add permission methods

* User add AclUserPermissions with test

* Group add AclGroupPermissions with test

* User and Group delete permissions methods

* Refactor of permission string extractor + getPermissions service methods

* Initial implementation of JSON token view getPermissions

* Better tests

* Uber test for getPermissions method on User

* User/Group controller getPermissions

* User/Group controller permissions create and delete

* AclEntity Controller

* Optimize imports

* Exposing docker postgres

* Better port

* Update mask handling

* AclMask from values

* Refactored handling of permissions mask

* AclMask fromValue test

* Removed no longer needed annotation

* Enum fix

* Tests updated

* Migrations (untested)

* Repository migration to UUID

* Models updates for UUID

* UUID updates continued

* Tests updated, application builds

* Migrations reworked

* Tests and failing methods updated

* Optimise imports

* Codacy tweaks

* Unused imports

* Refactor migration class

* Updated README for ACL addition (need to add new flyway command)

* Newline missing

* AclMask val

* missing newline
  • Loading branch information
lepsalex authored and andricDu committed Aug 13, 2018
1 parent 0fdd52d commit 6e1ba9a
Show file tree
Hide file tree
Showing 61 changed files with 2,073 additions and 236 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Here are some of the features of EGO:
* Single Sign on for microservices
* User-authentication through Federated Identities such as Google, Facebook, Github (Coming Soon), ORCID (Coming Soon)
* Uses JWT(Json Web Tokens) for Authorization Tokens
* Provides ability to create permission lists for users and/or groups on user-defined permission entities
* Built using well established Frameworks - Spring Boot, Spring Security

## Tech Stack
Expand Down Expand Up @@ -142,7 +143,9 @@ An example ego JWT is mentioned below:
"createdAt": "2017-11-23 10:24:41",
"lastLogin": "2017-11-23 11:23:58",
"preferredLanguage": null,
"roles": ["ADMIN"]
"roles": ["ADMIN"],
"groups": ["GroupOne", "GroupTwo"],
"permissions": ["Study001.WRITE", "Study002.DENY"]
}
}
}
Expand All @@ -153,4 +156,5 @@ An example ego JWT is mentioned below:
#### Notes
* "aud" field can contain one or more client IDs. This field indicates the client services that are authorized to use this JWT.
* "groups" will differ based on the domain of client services - each domain of service should get list of groups from that domain's ego service.
* "permissions" will differ based on domain of client service - each domain of service should get list of permissions from that domain's ego service.
* Unit Tests using testcontainers will also run flyway migrations to ensure database has the correct structure
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ services:
- ./src/main/resources/schemas/01-psql-schema.sql:/docker-entrypoint-initdb.d/init.sql
expose:
- "5432"
ports:
- "8432:5432"
9 changes: 8 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@
<version>42.1.4</version>
</dependency>

<!-- VLAAADDDD -->
<!-- https://vladmihalcea.com/the-best-way-to-map-an-enum-type-with-jpa-and-hibernate/ -->
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.2.2</version>
</dependency>

<!-- TestContainers (for testing) -->
<dependency>
<groupId>org.testcontainers</groupId>
Expand Down Expand Up @@ -127,7 +135,6 @@
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>5.1.4</version>
<scope>test</scope>
</dependency>


Expand Down
143 changes: 143 additions & 0 deletions src/main/java/db/migration/V1_1__complete_uuid_migration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package db.migration;

import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.flywaydb.core.api.migration.spring.SpringJdbcMigration;
import org.overture.ego.model.entity.Application;
import org.overture.ego.model.entity.Group;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.UUID;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

@Slf4j
public class V1_1__complete_uuid_migration implements SpringJdbcMigration {
public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
log.info("Flyway java migration: V1_1__complete_uuid_migration running ******************************");

// Development tests for migration left in for future use potentially
// This whole class can be refactored into a SQL based migration
boolean runWithTest = false;
UUID userOneId = UUID.randomUUID();
UUID userTwoId = UUID.randomUUID();

// Test data (if set to true)
if (runWithTest) {
createTestData(jdbcTemplate, userOneId, userTwoId);
}

jdbcTemplate.execute("CREATE EXTENSION \"uuid-ossp\"");

// Add temporary UUID column to applications and groups
jdbcTemplate.execute("ALTER TABLE EGOAPPLICATION ADD uuid UUID DEFAULT uuid_generate_v4()");
jdbcTemplate.execute("ALTER TABLE EGOGROUP ADD uuid UUID DEFAULT uuid_generate_v4()");

// Add temporary UUID column for mapping tables or tables with fk relationships
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION ADD grpUuid UUID");
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION ADD appUuid UUID");
jdbcTemplate.execute("ALTER TABLE USERGROUP ADD grpUuid UUID");
jdbcTemplate.execute("ALTER TABLE USERAPPLICATION ADD appUuid UUID");

// Drop fk contrainsts
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION DROP CONSTRAINT groupapplication_grpid_fkey");
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION DROP CONSTRAINT groupapplication_appid_fkey");
jdbcTemplate.execute("ALTER TABLE USERGROUP DROP CONSTRAINT usergroup_grpid_fkey");
jdbcTemplate.execute("ALTER TABLE USERAPPLICATION DROP CONSTRAINT userapplication_appid_fkey");

// Update fk mapping columns for applications and groups
jdbcTemplate.execute("UPDATE GROUPAPPLICATION SET grpUuid = EGOGROUP.uuid FROM EGOGROUP WHERE EGOGROUP.id = GROUPAPPLICATION.grpId");
jdbcTemplate.execute("UPDATE GROUPAPPLICATION SET appUuid = EGOAPPLICATION.uuid FROM EGOAPPLICATION WHERE EGOAPPLICATION.id = GROUPAPPLICATION.appId");
jdbcTemplate.execute("UPDATE USERGROUP SET grpUuid = EGOGROUP.uuid FROM EGOGROUP WHERE EGOGROUP.id = USERGROUP.grpId");
jdbcTemplate.execute("UPDATE USERAPPLICATION SET appUuid = EGOAPPLICATION.uuid FROM EGOAPPLICATION WHERE EGOAPPLICATION.id = USERAPPLICATION.appId");

// Clean up temporary columns for EGOAPPLICATION and re-add PK contraints
jdbcTemplate.execute("ALTER TABLE EGOAPPLICATION DROP CONSTRAINT EGOAPPLICATION_pkey");
jdbcTemplate.execute("ALTER TABLE EGOAPPLICATION DROP COLUMN id");
jdbcTemplate.execute("ALTER TABLE EGOAPPLICATION RENAME COLUMN uuid TO id");
jdbcTemplate.execute("ALTER TABLE EGOAPPLICATION ADD PRIMARY KEY (id)");

// Clean up temporary columns for EGOGROUP and re-add PK contraints
jdbcTemplate.execute("ALTER TABLE EGOGROUP DROP CONSTRAINT EGOGROUP_pkey");
jdbcTemplate.execute("ALTER TABLE EGOGROUP DROP COLUMN id");
jdbcTemplate.execute("ALTER TABLE EGOGROUP RENAME COLUMN uuid TO id");
jdbcTemplate.execute("ALTER TABLE EGOGROUP ADD PRIMARY KEY (id)");

// Clean up temporary columns for GROUPAPPLICATION and re-add FK contraints
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION DROP COLUMN grpId");
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION DROP COLUMN appId");
jdbcTemplate.execute("ALTER TABLE USERGROUP DROP COLUMN grpId");
jdbcTemplate.execute("ALTER TABLE USERAPPLICATION DROP COLUMN appId");

jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION RENAME COLUMN grpUuid TO grpId");
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION RENAME COLUMN appUuid TO appId");
jdbcTemplate.execute("ALTER TABLE USERGROUP RENAME COLUMN grpUuid TO grpId");
jdbcTemplate.execute("ALTER TABLE USERAPPLICATION RENAME COLUMN appUuid TO appId");

jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION ADD FOREIGN KEY (grpId) REFERENCES EGOGROUP (id)");
jdbcTemplate.execute("ALTER TABLE GROUPAPPLICATION ADD FOREIGN KEY (appId) REFERENCES EGOAPPLICATION (id)");
jdbcTemplate.execute("ALTER TABLE USERGROUP ADD FOREIGN KEY (grpId) REFERENCES EGOGROUP (id)");
jdbcTemplate.execute("ALTER TABLE USERAPPLICATION ADD FOREIGN KEY (appId) REFERENCES EGOAPPLICATION (id)");

// Test queries to ensure all is good (if flag set to true)
if (runWithTest) {
testUuidMigration(jdbcTemplate, userOneId, userTwoId);
}

log.info("****************************** Flyway java migration: V1_1__complete_uuid_migration complete");
}

private void createTestData(JdbcTemplate jdbcTemplate, UUID userOneId, UUID userTwoId) {
jdbcTemplate.update("INSERT INTO EGOUSER (id, name, email, status) VALUES (?, 'userOne', '[email protected]', 'Pending')", userOneId);
jdbcTemplate.update("INSERT INTO EGOUSER (id, name, email, status) VALUES (?, 'userTwo', '[email protected]', 'Pending')", userTwoId);

jdbcTemplate.execute("INSERT INTO EGOAPPLICATION (id, name, clientid, clientsecret, status) VALUES (1, 'appOne', '123', '321', 'Pending')");
jdbcTemplate.execute("INSERT INTO EGOAPPLICATION (id, name, clientid, clientsecret, status) VALUES (2, 'appTwo', '456', '654', 'Pending')");
jdbcTemplate.execute("INSERT INTO EGOAPPLICATION (id, name, clientid, clientsecret, status) VALUES (3, 'appThree', '789', '987', 'Pending')");

jdbcTemplate.execute("INSERT INTO EGOGROUP (id, name, status) VALUES (1, 'groupOne', 'Pending')");
jdbcTemplate.execute("INSERT INTO EGOGROUP (id, name, status) VALUES (2, 'groupTwo', 'Pending')");

jdbcTemplate.update("INSERT INTO USERGROUP (userid, grpid) VALUES (?, 1)", userOneId);
jdbcTemplate.update("INSERT INTO USERGROUP (userid, grpid) VALUES (?, 2)", userTwoId);

jdbcTemplate.update("INSERT INTO USERAPPLICATION (userid, appid) VALUES (?, 1)", userOneId);
jdbcTemplate.update("INSERT INTO USERAPPLICATION (userid, appid) VALUES (?, 2)", userTwoId);

jdbcTemplate.execute("INSERT INTO GROUPAPPLICATION (grpid, appid) VALUES (1, 1)");
jdbcTemplate.execute("INSERT INTO GROUPAPPLICATION (grpid, appid) VALUES (1, 2)");
jdbcTemplate.execute("INSERT INTO GROUPAPPLICATION (grpid, appid) VALUES (2, 3)");
}

private void testUuidMigration(JdbcTemplate jdbcTemplate, UUID userOneId, UUID userTwoId) {
val egoGroups = jdbcTemplate.query("SELECT * FROM EGOGROUP", new BeanPropertyRowMapper(Group.class));
val egoApplications = jdbcTemplate.query("SELECT * FROM EGOAPPLICATION", new BeanPropertyRowMapper(Application.class));
val userGroups = jdbcTemplate.queryForList("SELECT * FROM USERGROUP");
val userApplications = jdbcTemplate.queryForList("SELECT * FROM USERAPPLICATION");
val groupApplications = jdbcTemplate.queryForList("SELECT * FROM GROUPAPPLICATION");

val groupOneId = ((Group) egoGroups.get(0)).getId();
val groupTwoId = ((Group) egoGroups.get(1)).getId();

val appOneId = ((Application) egoApplications.get(0)).getId();
val appTwoId = ((Application) egoApplications.get(1)).getId();
val appThreeId = ((Application) egoApplications.get(2)).getId();

assertThat(groupApplications.get(0).get("grpId").toString(), is(groupOneId.toString()));
assertThat(groupApplications.get(0).get("appId").toString(), is(appOneId.toString()));

assertThat(groupApplications.get(1).get("grpId").toString(), is(groupOneId.toString()));
assertThat(groupApplications.get(1).get("appId").toString(), is(appTwoId.toString()));

assertThat(groupApplications.get(2).get("grpId").toString(), is(groupTwoId.toString()));
assertThat(groupApplications.get(2).get("appId").toString(), is(appThreeId.toString()));

assertThat(userGroups.get(0).get("userid").toString(), is(userOneId.toString()));
assertThat(userGroups.get(1).get("userid").toString(), is(userTwoId.toString()));

assertThat(userApplications.get(0).get("userid").toString(), is(userOneId.toString()));
assertThat(userApplications.get(1).get("userid").toString(), is(userTwoId.toString()));
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/overture/ego/config/AuthConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

import lombok.extern.slf4j.Slf4j;
import org.overture.ego.security.CorsFilter;
import org.overture.ego.token.CustomTokenEnhancer;
import org.overture.ego.service.ApplicationService;
import org.overture.ego.token.CustomTokenEnhancer;
import org.overture.ego.token.signer.TokenSigner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package org.overture.ego.config;

import lombok.SneakyThrows;
import org.overture.ego.security.*;
import org.overture.ego.security.AuthorizationManager;
import org.overture.ego.security.JWTAuthorizationFilter;
import org.overture.ego.security.SecureAuthorizationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/overture/ego/config/ServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

package org.overture.ego.config;

import org.overture.ego.security.*;
import org.overture.ego.security.AuthorizationManager;
import org.overture.ego.security.DefaultAuthorizationManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


import java.util.List;

@Configuration
Expand Down
100 changes: 100 additions & 0 deletions src/main/java/org/overture/ego/controller/AclEntityController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.overture.ego.controller;

import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import lombok.extern.slf4j.Slf4j;
import org.overture.ego.model.dto.PageDTO;
import org.overture.ego.model.entity.AclEntity;
import org.overture.ego.model.search.Filters;
import org.overture.ego.model.search.SearchFilter;
import org.overture.ego.security.AdminScoped;
import org.overture.ego.service.AclEntityService;
import org.overture.ego.view.Views;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/acl-entity")
public class AclEntityController {

@Autowired
private AclEntityService aclEntityService;

@AdminScoped
@RequestMapping(method = RequestMethod.GET, value = "")
@ApiImplicitParams({
@ApiImplicitParam(name = "limit", dataType = "string", paramType = "query",
value = "Number of results to retrieve"),
@ApiImplicitParam(name = "offset", dataType = "string", paramType = "query",
value = "Index of first result to retrieve"),
@ApiImplicitParam(name = "sort", dataType = "string", paramType = "query",
value = "Field to sort on"),
@ApiImplicitParam(name = "sortOrder", dataType = "string", paramType = "query",
value = "Sorting order: ASC|DESC. Default order: DESC"),
@ApiImplicitParam(name = "status", dataType = "string", paramType = "query",
value = "Filter by status. " +
"You could also specify filters on any field of the entity being queried as " +
"query parameters in this format: name=something")

})
@ApiResponses(
value = {
@ApiResponse(code = 200, message = "Page of ACL Entities", response = PageDTO.class)
}
)
@JsonView(Views.REST.class)
public @ResponseBody
PageDTO<AclEntity> getAclEntityList(
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = true) final String accessToken,
@ApiIgnore @Filters List<SearchFilter> filters,
Pageable pageable) {
return new PageDTO<>(aclEntityService.listAclEntities(filters, pageable));
}

@AdminScoped
@RequestMapping(method = RequestMethod.POST, value = "")
@ApiResponses(
value = {
@ApiResponse(code = 200, message = "New ACL Entity", response = AclEntity.class)
}
)
public @ResponseBody
AclEntity create(
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = true) final String accessToken,
@RequestBody(required = true) AclEntity aclEntity) {
return aclEntityService.create(aclEntity);
}

@AdminScoped
@RequestMapping(method = RequestMethod.PUT, value = "/{id}")
@ApiResponses(
value = {
@ApiResponse(code = 200, message = "Updated ACL Entity", response = AclEntity.class)
}
)
public @ResponseBody
AclEntity update(
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = true) final String accessToken,
@RequestBody(required = true) AclEntity updatedAclEntity) {
return aclEntityService.update(updatedAclEntity);
}

@AdminScoped
@RequestMapping(method = RequestMethod.DELETE, value = "/{id}")
@ResponseStatus(value = HttpStatus.OK)
public void delete(
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = true) final String accessToken,
@PathVariable(value = "id", required = true) String id) {
aclEntityService.delete(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.overture.ego.provider.google.GoogleTokenService;
import org.overture.ego.provider.facebook.FacebookTokenService;
import org.overture.ego.provider.google.GoogleTokenService;
import org.overture.ego.token.TokenService;
import org.overture.ego.token.signer.TokenSigner;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down
Loading

0 comments on commit 6e1ba9a

Please sign in to comment.