Skip to content

Commit

Permalink
Resolving PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
nitinks-ee committed Nov 15, 2021
1 parent 2c3d8a9 commit fcb5e4f
Show file tree
Hide file tree
Showing 85 changed files with 91 additions and 81 deletions.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
KEYCLOAK_SERVER_PORT=8180
KEYCLOAK_USER=[email protected]
KEYCLOAK_PASSWORD=ac0n3x72
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,5 @@ build/
.project
.settings
.vscode
.env
/src/launcher/bin
*.classpath
19 changes: 8 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,20 @@ Previously known as Axon Framework, [Axon](https://axoniq.io/) is a framework fo
[aggregates](https://www.martinfowler.com/bliki/DDD_Aggregate.html) and
[CQRS](https://www.martinfowler.com/bliki/CQRS.html).

DDD is, at its core, about **linguistics**. Establishing a ubiquitous language helps identify sources of overlap or
tension in conceptual understanding that **may be** indicative of a separation of concern in a system. Rather than
DDD is, at its core, about _linguistics_. Establishing a ubiquitous language helps identify sources of overlap or
tension in conceptual understanding that _may be_ indicative of a separation of concern in a system. Rather than
attempting to model a domain in intricate detail inside a common model, DDD places great emphasis on identifying these
boundaries in order to define [bounded contexts](https://www.martinfowler.com/bliki/BoundedContext.html). These reduce
complexity of the system by avoiding [anemic domain models](https://www.martinfowler.com/bliki/AnemicDomainModel.html)
due to a slow migration of complex domain logic from within the domain model to helper classes on the periphery as the
system evolves.

Event sourcing captures the activities of a business in an event log, an append-only history of every important
**business action** that has ever been taken by users or by the system itself. Events are mapped to an arbitrary number
_business action_ that has ever been taken by users or by the system itself. Events are mapped to an arbitrary number
of projections for use by the query side of the system. Being able to replay events offers several significant benefits:

- Projections can be optimised for reading by denormalising data
- Events can be **upcasted**. That is, events are marked with a revision that allows them to be transformed to an updated
- Events can be _upcasted_. That is, events are marked with a revision that allows them to be transformed to an updated
version of the same event. This protects developers from creating significant errors in users' data due to, for example,
accidentally transposing two fields within a command or event;
- Projections can be updated with new information that was either captured by or derived from events. New business
Expand Down Expand Up @@ -230,7 +230,7 @@ overhead.

There is a philosophical argument for defining aggregates such that all information required to validate commands is
held by an aggregate in memory. In practice, however, more natural aggregates can be formed by allowing some validation
to be based on **projections**. We also know from experience that some validation will be shared among multiple aggregates.
to be based on _projections_. We also know from experience that some validation will be shared among multiple aggregates.
The amount of testing required to verify all possible command failure situations tends to grow non-linearly as the number
of checks that are performed inside an aggregate grows.

Expand Down Expand Up @@ -308,9 +308,7 @@ explains how it works, its limitations and an important caveat.

## Security and access control

[Keycloak](https://www.keycloak.org/) is an open-source identity and access management solution aimed at modern applications and services. It makes it easy to secure applications and services with little to no code.

We are using Keycloak to manage users' authentication, authorization, and session management.
We are using [Keycloak](https://www.keycloak.org/) to manage users' authentication, authorization, and session management.

Keycloak has the following three main concepts.

Expand All @@ -320,11 +318,10 @@ Keycloak has the following three main concepts.

_Note: Our app has some internal roles for managing the user's access levels but those roles don't have any relation with Keycloak roles._

### User and Authentication Token
### User and Token

A user object in the business domain often requires more attributes than a user object from the Keycloak authentication token.
For an example, the starter kit's user object has an extra `organizationId` property. To bridge this difference for
authentication context, we added this info in the Keycloak authentication token as other claims, and we can access these claims like remaining claims of a user object.
For an example, the starter kit's user object has an extra `organizationId` attribute, to authorize the user, we added this info in the Keycloak authentication token as other claims, and we can access these claims like remaining claims of a user object.

### Endpoint access control

Expand Down
2 changes: 0 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ subprojects {
javaXPersistenceVersion = '2.2'
jsonHelperVersion = '20210307'
keycloakVersion = '15.0.2'
keycloakRestClientVersion = '15.0.2'
liquibaseVersion = '4.4.0'
lombokVersion = '1.18.20'
microMeterPrometheusVersion = '1.7.1'
Expand All @@ -73,7 +72,6 @@ subprojects {
h2Version = '1.4.200'
hamcrestVersion = '2.2'
junitVersion = '5.7.2'
keycloakTestServerVersion = '15.0.2'
mockitoVersion = '3.11.2'
undercouchVersion = '4.1.2'
}
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ services:
command:
-Djboss.http.port=${KEYCLOAK_SERVER_PORT}
volumes:
- ./imports:/opt/jboss/keycloak/imports
- ./keycloak-themes/custom:/opt/jboss/keycloak/themes/custom
- ./keycloak/imports:/opt/jboss/keycloak/imports
- ./keycloak/themes/custom:/opt/jboss/keycloak/themes/custom
environment:
DB_VENDOR: POSTGRES
DB_ADDR: postgres-db
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
package engineering.everest.lhotse.api.config;

import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;

import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.OAuthBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.service.GrantType;
import springfox.documentation.service.ResourceOwnerPasswordCredentialsGrant;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static springfox.documentation.builders.PathSelectors.regex;

import java.util.Arrays;
import java.util.Set;
import java.util.function.Predicate;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static springfox.documentation.builders.PathSelectors.regex;

@Profile("!prod")
@Configuration
@ComponentScan({"engineering.everest.lhotse.api"})
Expand All @@ -42,18 +41,22 @@
@Tag(name = "OrgAdmins", description = "Organization Admin information and management"),
})
public class SwaggerConfig {
@Value("${keycloak.auth-server-url}")

private String authServer;

@Value("${keycloak.credentials.secret}")
private String clientSecret;

@Value("${keycloak.resource}")
private String cliendId;

@Value("${keycloak.realm}")
private String realm;

public SwaggerConfig(@Value("${keycloak.auth-server-url}") String authServer,
@Value("${keycloak.credentials.secret}") String clientSecret,
@Value("${keycloak.resource}") String cliendId,
@Value("${keycloak.realm}") String realm) {
this.authServer = authServer;
this.cliendId = cliendId;
this.clientSecret = clientSecret;
this.realm = realm;
}

@Bean
public Docket apiDocumentation() {
return new Docket(DocumentationType.SWAGGER_2)
Expand Down Expand Up @@ -114,7 +117,6 @@ private SecurityContext securityContext() {
private AuthorizationScope[] scopes() {
return new AuthorizationScope[] {
new AuthorizationScope("read", "for read operations"),
new AuthorizationScope("write", "for write operations"),
new AuthorizationScope("foo", "Access foo API") };
new AuthorizationScope("write", "for write operations") };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ public AdminOrganizationsController(DtoConverter dtoConverter, OrganizationsServ
@ApiOperation("Retrieves details of all organizations")
@AdminOnly
public List<OrganizationResponse> getAllOrganizations() {
return organizationsReadService.getOrganizations().stream().map(dtoConverter::convert).collect(toList());
return organizationsReadService.getOrganizations().stream()
.map(dtoConverter::convert)
.collect(toList());
}

@PostMapping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ public void updateOrganization(@ApiIgnore Principal principal, @PathVariable UUI
@ApiOperation("Retrieve a list of users for an organization")
@AdminOrUserOfTargetOrganization
public List<UserResponse> listOrganizationUsers(@ApiIgnore Principal principal, @PathVariable UUID organizationId) {
return usersReadService.getUsersForOrganization(organizationId).stream().map(dtoConverter::convert)
return usersReadService.getUsersForOrganization(organizationId)
.stream().map(dtoConverter::convert)
.collect(toList());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ public ResponseEntity<StreamingResponseBody> streamProfilePhoto(@ApiIgnore Princ
inputStream.transferTo(outputStream);
}
};
return ResponseEntity.ok().contentType(APPLICATION_OCTET_STREAM).body(streamingResponse);
return ResponseEntity.ok()
.contentType(APPLICATION_OCTET_STREAM)
.body(streamingResponse);
}

@GetMapping(value = "/profile-photo/thumbnail", produces = APPLICATION_OCTET_STREAM_VALUE)
Expand All @@ -89,6 +91,8 @@ public ResponseEntity<StreamingResponseBody> streamProfilePhotoThumbnail(@ApiIgn
inputStream.transferTo(outputStream);
}
};
return ResponseEntity.ok().contentType(APPLICATION_OCTET_STREAM).body(streamingResponse);
return ResponseEntity.ok()
.contentType(APPLICATION_OCTET_STREAM)
.body(streamingResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,26 @@
import engineering.everest.lhotse.users.services.UsersService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import springfox.documentation.annotations.ApiIgnore;

import org.springframework.beans.factory.annotation.Autowired;

import javax.validation.Valid;
import java.security.Principal;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import static java.util.stream.Collectors.toList;
import static org.springframework.http.HttpStatus.OK;

import java.security.Principal;

@RestController
@RequestMapping("/api/users")
@Api(tags = "Users")
Expand All @@ -54,7 +52,9 @@ public UsersController(DtoConverter dtoConverter, UsersService usersService, Use
@ApiOperation("Retrieves entire user list for all organisations")
@AdminOnly
public List<UserResponse> getAllUsers() {
return usersReadService.getUsers().stream().map(dtoConverter::convert).collect(toList());
return usersReadService.getUsers()
.stream().map(dtoConverter::convert)
.collect(toList());
}

@PostMapping("/{userId}/forget")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.*;
import java.util.UUID;

import static org.mockito.Mockito.mock;
import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;
Expand Down
2 changes: 1 addition & 1 deletion src/common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dependencies {
implementation "org.springframework.boot:spring-boot-starter-json:${springBootVersion}"
implementation "org.apache.commons:commons-lang3:${commonsLangVersion}"
implementation "org.json:json:${jsonHelperVersion}"
implementation "org.keycloak:keycloak-admin-client:${keycloakRestClientVersion}"
implementation "org.keycloak:keycloak-admin-client:${keycloakVersion}"
implementation "org.springframework.boot:spring-boot-starter-webflux:${webfluxVersion}"

testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package engineering.everest.lhotse.axon.common.services;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import engineering.everest.lhotse.axon.common.domain.Role;
import engineering.everest.lhotse.axon.common.domain.UserAttribute;
import engineering.everest.lhotse.axon.common.exceptions.KeycloakSynchronizationException;
Expand All @@ -20,28 +14,42 @@
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

import reactor.core.publisher.Mono;

import java.util.UUID;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;

@Slf4j
@Component
public class KeycloakSynchronizationService {
private static final String BEARER = "Bearer ";
private static final String AUTHORIZATION = "Authorization";
private static final String VALUE_KEY = "value";

@Value("${keycloak.auth-server-url}")
private String keycloakServerAuthUrl;
@Value("${kc.server.admin-user}")
private String keycloakAdminUser;
@Value("${kc.server.admin-password}")
private String keycloakAdminPassword;
@Value("${kc.server.master-realm.default.client-id}")
private String keycloakMasterRealmAdminClientId;
@Value("${keycloak.resource}")
private String keycloakDefaultRealmDefaultClientId;
@Value("${kc.server.connection.pool-size}")
private int keycloakServerConnectionPoolSize;

private final String keycloakServerAuthUrl;
private final String keycloakAdminUser;
private final String keycloakAdminPassword;
private final String keycloakMasterRealmAdminClientId;
private final String keycloakDefaultRealmDefaultClientId;
private final int keycloakServerConnectionPoolSize;

public KeycloakSynchronizationService(@Value("${keycloak.auth-server-url}") String keycloakServerAuthUrl,
@Value("${kc.server.admin-user}") String keycloakAdminUser,
@Value("${kc.server.admin-password}") String keycloakAdminPassword,
@Value("${kc.server.master-realm.default.client-id}") String keycloakMasterRealmAdminClientId,
@Value("${keycloak.resource}") String keycloakDefaultRealmDefaultClientId,
@Value("${kc.server.connection.pool-size}") int keycloakServerConnectionPoolSize) {
this.keycloakServerAuthUrl = keycloakServerAuthUrl;
this.keycloakAdminUser = keycloakAdminUser;
this.keycloakAdminPassword = keycloakAdminPassword;
this.keycloakMasterRealmAdminClientId = keycloakMasterRealmAdminClientId;
this.keycloakDefaultRealmDefaultClientId = keycloakDefaultRealmDefaultClientId;
this.keycloakServerConnectionPoolSize = keycloakServerConnectionPoolSize;
}

private Keycloak getAdminKeycloakClientInstance() {
return KeycloakBuilder.builder().serverUrl(keycloakServerAuthUrl).grantType(OAuth2Constants.PASSWORD)
Expand Down
10 changes: 5 additions & 5 deletions src/launcher/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ dependencies {
testImplementation "org.apache.commons:commons-text:${commonsTextVersion}"
testImplementation "org.bouncycastle:bcpkix-jdk15on:${bouncyCastleVersion}"
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'
testImplementation "org.keycloak:keycloak-admin-client:${keycloakRestClientVersion}"
testImplementation "org.keycloak:keycloak-admin-client:${keycloakVersion}"
}

dependencyManagement {
Expand All @@ -88,8 +88,8 @@ dependencyManagement {
}

task downloadKeycloakZipFile(type: Download) {
src "https://github.com/keycloak/keycloak/releases/download/${keycloakTestServerVersion}/keycloak-${keycloakTestServerVersion}.zip"
dest new File(buildDir, "keycloak-${keycloakTestServerVersion}.zip")
src "https://github.com/keycloak/keycloak/releases/download/${keycloakVersion}/keycloak-${keycloakVersion}.zip"
dest new File(buildDir, "keycloak-${keycloakVersion}.zip")
onlyIfModified true
}

Expand All @@ -100,7 +100,7 @@ task downloadAndUnzipKeycloakFile(dependsOn: downloadKeycloakZipFile, type: Copy

task startServer(dependsOn: 'downloadAndUnzipKeycloakFile') {
doLast {
def keycloakDir = "$buildDir/keycloak-${keycloakTestServerVersion}"
def keycloakDir = "$buildDir/keycloak-${keycloakVersion}"
def port = 8180
def waitTime = 10000
def testUser = "[email protected]"
Expand All @@ -114,7 +114,7 @@ task startServer(dependsOn: 'downloadAndUnzipKeycloakFile') {
startServer.consumeProcessOutput(System.out, System.err)
startServer.waitForOrKill(3 * waitTime)

def createRealm = "$keycloakDir/bin/kcadm.sh create realms -f $rootDir/imports/realm-export.json --no-config --server http://localhost:$port/auth --realm master --user $testUser --password $testPass".execute()
def createRealm = "$keycloakDir/bin/kcadm.sh create realms -f $rootDir/keycloak/imports/realm-export.json --no-config --server http://localhost:$port/auth --realm master --user $testUser --password $testPass".execute()
createRealm.consumeProcessOutput(System.out, System.err)
createRealm.waitForOrKill(waitTime)
}
Expand Down
Loading

0 comments on commit fcb5e4f

Please sign in to comment.