Skip to content

Commit

Permalink
Merge pull request RADAR-base#310 from RADAR-base/release-0.5.0
Browse files Browse the repository at this point in the history
Release 0.5.0
  • Loading branch information
dennyverbeeck authored Aug 29, 2018
2 parents aecf0ea + 2ddb145 commit 7d1707a
Show file tree
Hide file tree
Showing 114 changed files with 3,119 additions and 1,276 deletions.
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
build
out
radar-auth/build
radar-auth/out
radar-auth/src/test
.idea
.gradle
.git
node_modules
oauth-client-util/build
oauth-client-util/out
oauth-client-util/src/test
src/test/java
src/gatling
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ root = true
# Change these settings to your own preference
indent_style = space
indent_size = 4
continuation_indent_size = 8

# We recommend you to keep these unchanged
end_of_line = lf
Expand All @@ -22,3 +23,4 @@ trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ script:
- ./gradlew generateJavaClient
- killall java # stop running MP instance
- ./src/test/bash/run-prod-e2e.sh
- ./src/test/bash/test-docker-build-on-release-branch.sh
- echo "include 'managementportal-client'" >> settings.gradle # make this a sub-project so we can build artifacts and javadoc easily
after_script:
- ./gradlew sendCoverageToCodacy
Expand Down
19 changes: 14 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,25 @@ RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \

# installing the node packages before adding the src directory will allow us to re-use these image layers when only the souce code changes
WORKDIR /app
COPY build.gradle gradle.properties gradlew package.json postcss.config.js proxy.conf.json settings.gradle tsconfig-aot.json tsconfig.json tslint.json yarn.lock /app/
ENV GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.project.prod=true"
COPY gradlew /app/
COPY gradle/wrapper gradle/wrapper
RUN ./gradlew --version

COPY gradle gradle
COPY build.gradle gradle.properties settings.gradle /app/
COPY radar-auth/build.gradle radar-auth/
COPY oauth-client-util/build.gradle oauth-client-util/
RUN ./gradlew downloadDependencies

COPY package.json postcss.config.js proxy.conf.json tsconfig-aot.json tsconfig.json tslint.json yarn.lock /app/
COPY webpack webpack
COPY radar-auth radar-auth
COPY oauth-client-util oauth-client-util
RUN ./gradlew --no-daemon -s -Pprod npmInstall
RUN ./gradlew -s npmInstall

# now we copy our application source code and build it
COPY radar-auth radar-auth
COPY src src
RUN ./gradlew --no-daemon -s -Pprod bootRepackage
RUN ./gradlew -s bootRepackage

# Run stage
FROM openjdk:8-jre-alpine
Expand Down
56 changes: 39 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,57 +94,75 @@ for other options on overriding the default configuration.
| `SPRING_DATASOURCE_URL` | `jdbc:postgresql://localhost:5432/managementportal` | URL for the database to be used |
| `SPRING_DATASOURCE_USERNAME` | `<username>` | Username to access the database |
| `SPRING_DATASOURCE_PASSWORD` | `<password>` | Password to access the database |
| `SPRING_APPLICATION_JSON` | None | Generic environment variable for overriding all types of application settings |
| `MANAGEMENTPORTAL_FRONTEND_CLIENT_SECRET` | None, you need to override this | OAuth client secret for the frontend |
| `MANAGEMENTPORTAL_FRONTEND_ACCESS_TOKEN_VALIDITY_SECONDS` | `14400` | Frontend access token validity period in seconds |
| `MANAGEMENTPORTAL_FRONTEND_REFRESH_TOKEN_VALIDITY_SECONDS` | `259200` | Frontend refresh token validity period in seconds |
| `MANAGEMENTPORTAL_OAUTH_CLIENTS_FILE` | `/mp-includes/config/oauth_client_details.csv` | Location of the OAuth clients file |
| `MANAGEMENTPORTAL_OAUTH_KEY_STORE_PASSWORD` | `radarbase` | Password for the JKS keystore |
| `MANAGEMENTPORTAL_OAUTH_SIGNING_KEY_ALIAS` | `radarbase-managementportal-ec` | Alias in the keystore of the keypair to use for signing |
| `MANAGEMENTPORTAL_OAUTH_CHECKING_KEY_ALIASES_0` | None | Alias in the keystore of the public key to use for checking. Define multiple aliases by increasing number suffix (i.e. setting `MANAGEMENTPORTAL_OAUTH_CHECKING_KEY_ALIASES_1`, `MANAGEMENTPORTAL_OAUTH_CHECKING_KEY_ALIASES_2` etc.). If you do not set a list of checking key aliases, the public key of the signing keypair will be used for checking signatures. |
| `MANAGEMENTPORTAL_CATALOGUE_SERVER_ENABLE_AUTO_IMPORT` | `false` | Wether to enable or disable auto import of sources from the catalogue server |
| `MANAGEMENTPORTAL_CATALOGUE_SERVER_SERVER_URL` | None | URL to the catalogue server |
| `MANAGEMENTPORTAL_COMMON_BASE_URL` | None | Resolvable baseUrl of the hosted platform |
| `MANAGEMENTPORTAL_COMMON_MANAGEMENT_PORTAL_BASE_URL` | None | Resolvable baseUrl of this managementportal instance |
| `MANAGEMENTPORTAL_COMMON_PRIVACY_POLICY_URL` | None | Resolvable URL to the common privacy policy url |
| `MANAGEMENTPORTAL_COMMON_ADMIN_PASSWORD` | None | Admin password |
| `JHIPSTER_SLEEP` | `10` | Time in seconds that the application should wait at bootup. Used to allow the database to become ready |
| `JAVA_OPTS` | `-Xmx512m` | Options to pass on the JVM |

Lists cannot directly be encoded by environment variables in this version of Spring. So for example the OAuth checking key aliases need to be encoded using the `SPRING_APPLICATION_JSON` variable. For setting two aliases, set it to `{"managementportal":{"oauth":{"checkingKeyAliases":["one","two"]}}}`, for example. If this list is not set, the signing key will also be used as the checking key.

### OAuth Clients

ManagementPortal uses `OAuth2` workflow to provide authentication and authorization. To add new OAuth clients, you can add at runtime through the UI, or you can add them to the OAuth clients file
referenced by the `MANAGEMENTPORTAL_OAUTH_CLIENTS_FILE` configuration option.
- If your client is supposed to work with the `Pair app` feature, you need to set a key called `dynamic_registration` to `true` like this `{"dynamic_registration": true}` in its `additional_information` map. See the aRMT and pRMT
clients for an example.
- If your client is `dynamic_registration` enabled, the QR code generated by `Pair app` feature will contain a short-living URL. By doing a `GET` request on that URL the `refresh-token` and related meta-data can be fetched.
- If you want to prevent an OAuth client from being altered through the UI, you can add a key `{"protected": true}` in the `additional_information` map.

If the app is paired via the Pair App dialog, the QR code that will be scanned contains a refresh token.
```
{
"refreshToken": "..."
}
```
The app can use that refresh token to get new access and refresh tokens:
If the app is paired via the Pair App dialog, the QR code that will be scanned contains a short-lived URL, e.g. `https://radar-base-url.org/api/meta-token/bMUkowOmTOci`

Your app should access the URL, where it will receive an OAuth2
refresh token as well as the platform's base URL and a URL to the privacy policy. No authorization
is required to access this URL. **Important:** For security reasons, the information at this URL can
only be accessed once. Once it has been accessed it can not be retrieved again.

The app can use that refresh token to get new access and refresh tokens by doing the following HTTP
request to the base URL, using HTTP basic authentication with your OAuth client ID as username, and
an empty password.
```
POST MyId:MySecret /oauth/token
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=...
grant_type=refresh_token&refresh_token=<refresh_token>
```
This will respond with the access token and refresh token:
This will respond with at least the access token and refresh token:
```json
{
"access_token": "...",
"refresh_token": "...",
"expires_in": 14400
}
```
For the next request, you need to use the `refresh_token` that was returned rather than the one in the QR code, since refresh tokens are valid only once.
Both tokens are valid for a limited time only. When the access token runs out, you will need to
perform another request like the one above, but you need to use the new `refresh_token`, since
refresh tokens are valid only once.

The code grant flow for OAuth2 clients can be the following:
1. Ask user confirmation for your app:
```
GET /oauth/confirm_access?client_id=MyId&client_secret=MySecret&grant_type=code&redirect_uri=https://my.example.com/oauth_redirect
GET /oauth/confirm_access?client_id=MyId&grant_type=code&redirect_uri=https://my.example.com/oauth_redirect
```
This needs to be done from a interactive web view, either a browser or a web window. If the user approves, this will redirect to `https://my.example.com/oauth_redirect?code=abcdef`. In Android, with [https://appauth.io](AppAuth library), the URL could be `com.example.my://oauth_redirect` for the `com.example.my` app.
2. Request a token for you app:
where you replace `MyId` with your OAuth client id. This needs to be done from a interactive
web view, either a browser or a web window. If the user approves, this will redirect to
`https://my.example.com/oauth_redirect?code=abcdef`. In Android, with [https://appauth.io]
(AppAuth library), the URL could be `com.example.my://oauth_redirect` for the `com.example.my`
app.
2. Request a token for your app by doing a POST, again with HTTP basic authentication with as
username your OAuth client id, and leaving the password empty:
```
POST MyId:MySecret /oauth/token
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=abcdef&redirect_uri=https://my.example.com/oauth_redirect
Expand Down Expand Up @@ -230,11 +248,14 @@ will generate few files:
create src/main/webapp/app/my-component/my-component.component.ts
update src/main/webapp/app/app.module.ts
## Building for production
## On Production
### Building for production
To optimize the ManagementPortal application for production, run:
./gradlew -Pprod clean bootRepackage
### Hosting in production
The latest Meta-QR code implementation requires REST resources on `api/meta-token/*` should definitely be rate-limited by upstream servers.
This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files.
To ensure everything worked, run:
Expand Down Expand Up @@ -337,3 +358,4 @@ The resulting file can be imported into the [Swagger editor], or used with [Swag
[OpenAPI]: https://www.openapis.org/
[Swagger editor]: http://editor.swagger.io/
[Swagger codegen]: https://swagger.io/swagger-codegen/
[OAuth2 spec]: https://tools.ietf.org/html/rfc6749#section-9
53 changes: 34 additions & 19 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ plugins {
allprojects {
group 'org.radarcns'

version '0.4.1' // project version
version '0.5.0' // project version

// The comment on the previous line is only there to identify the project version line easily
// with a sed command, to auto-update the version number with the prepare-release-branch.sh
Expand Down Expand Up @@ -139,6 +139,27 @@ apply plugin: 'com.moowork.node'
apply plugin: 'io.spring.dependency-management'
defaultTasks 'bootRun'

configurations {
codacy
providedRuntime
compile.exclude module: "spring-boot-starter-tomcat"
cucumberTestCompile.extendsFrom testCompile
cucumberTestRuntime.extendsFrom testRuntime
}

sourceSets {
cucumberTest {
java {
compileClasspath += main.output + test.output + test.compileClasspath
runtimeClasspath += main.output + test.output + test.compileClasspath
srcDir file('src/cucumberTest/java')
}
resources {
srcDir 'src/cucumberTest/resources'
}
}
}

bootRepackage {
mainClass = 'org.radarcns.management.ManagementPortalApp'
}
Expand Down Expand Up @@ -176,20 +197,13 @@ if (OperatingSystem.current().isWindows()) {
}
}



test {
include '**/*UnitTest*'
include '**/*IntTest*'

// uncomment if the tests reports are not generated
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
// ignoreFailures true
reports.html.enabled = true
}

task cucumberTest(type: Test) {
include '**/CucumberTest*'
testClassesDirs = sourceSets.cucumberTest.output.classesDirs
classpath = sourceSets.cucumberTest.runtimeClasspath

testLogging {
events "skipped", "failed", "passed"
}

// uncomment if the tests reports are not generated
// see https://github.com/jhipster/generator-jhipster/pull/2771 and https://github.com/jhipster/generator-jhipster/pull/4484
Expand Down Expand Up @@ -228,11 +242,6 @@ ext.codacyVersion = '2.0.1'

description = ''

configurations {
codacy
providedRuntime
compile.exclude module: "spring-boot-starter-tomcat"
}

repositories {
mavenCentral()
Expand Down Expand Up @@ -526,3 +535,9 @@ artifactory {
artifactoryPublish {
publications('ManagementPortalPublication')
}

task downloadDependencies {
description "Pre-downloads dependencies"
configurations.compileClasspath.files
configurations.runtimeClasspath.files
}
6 changes: 1 addition & 5 deletions gradle/profile_prod.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ ext {
swaggerTargetFolder = "managementportal-client"
}

dependencies {

}

def profiles = 'prod'
if (project.hasProperty('no-liquibase')) {
profiles += ',no-liquibase'
Expand Down Expand Up @@ -44,7 +40,7 @@ processResources {
it.replace('#spring.profiles.active#', profiles)
}
filter {
it.replace('#project.version#', version)
it.replace('#project.version#', version.toString())
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion oauth-client-util/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Quickstart:

```groovy
dependencies {
compile group: 'org.radarcns', name: 'oauth-client-util', version: '0.4.1'
compile group: 'org.radarcns', name: 'oauth-client-util', version: '0.5.0'
}
```

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "management-portal",
"version": "0.4.1",
"version": "0.5.0",
"description": "Description for ManagementPortal",
"private": true,
"cacheDirectories": [
Expand Down Expand Up @@ -70,7 +70,7 @@
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "2.0.3",
"lazypipe": "1.0.1",
"lodash": "4.17.4",
"lodash": "4.17.5",
"map-stream": "0.0.7",
"node-sass": "^4.5.0",
"phantomjs-prebuilt": "2.1.14",
Expand Down Expand Up @@ -116,6 +116,6 @@
"test": "yarn run lint && karma start src/test/javascript/karma.conf.js",
"test:watch": "karma start --watch",
"e2e": "protractor src/test/javascript/protractor.conf.js",
"postinstall": "webdriver-manager update && yarn run webpack:build"
"postinstall": "webdriver-manager update --gecko false && yarn run webpack:build"
}
}
2 changes: 1 addition & 1 deletion radar-auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Add the dependency to your project.

Gradle:
```groovy
compile group: 'org.radarcns', name: 'radar-auth', version: '0.4.1'
compile group: 'org.radarcns', name: 'radar-auth', version: '0.5.0'
```

The library expects the identity server configuration in a file called `radar-is.yml`. Either set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collection;
import java.util.stream.Stream;
import org.radarcns.auth.config.ServerConfig;
import org.radarcns.auth.config.YamlServerConfig;
import org.radarcns.auth.exception.TokenValidationException;
Expand Down Expand Up @@ -175,18 +177,14 @@ private List<JWTVerifier> loadVerifiers() throws TokenValidationException {
lastFetch = Instant.now();
}

List<Algorithm> algorithms = new LinkedList<>();
if (config.getPublicKeyEndpoints() != null) {
algorithms.addAll(config.getPublicKeyEndpoints().stream()
.map(this::algorithmFromServerPublicKey).collect(Collectors.toList()));
}
if (config.getPublicKeys() != null) {
algorithms.addAll(config.getPublicKeys().stream()
.map(this::algorithmFromString).collect(Collectors.toList()));
}
Stream<Algorithm> endpointKeys = streamEmptyIfNull(config.getPublicKeyEndpoints())
.map(this::algorithmFromServerPublicKey);

Stream<Algorithm> stringKeys = streamEmptyIfNull(config.getPublicKeys())
.map(this::algorithmFromString);

// Create a verifier for each signature verification algorithm we created
return algorithms.stream()
return Stream.concat(endpointKeys, stringKeys)
.map(alg -> JWT.require(alg)
.withAudience(config.getResourceName())
.build())
Expand All @@ -206,8 +204,8 @@ private Algorithm algorithmFromServerPublicKey(URI serverUri) throws TokenValida
String alg = publicKeyInfo.get("alg").asText();
String pk = publicKeyInfo.get("value").asText();
return algorithmList.stream()
.filter(algorithm -> algorithm.getJwtAlgorithm().equals(alg))
.filter(algorithm -> pk.startsWith(algorithm.getKeyHeader()))
.filter(algorithm -> algorithm.getJwtAlgorithm().equals(alg)
&& pk.startsWith(algorithm.getKeyHeader()))
.findFirst()
.orElseThrow(() -> new TokenValidationException("The identity server "
+ "reported an unsupported signing algorithm: " + alg))
Expand All @@ -226,4 +224,8 @@ private Algorithm algorithmFromString(String publicKey) {
+ publicKey))
.getAlgorithm(publicKey);
}

private static <T> Stream<T> streamEmptyIfNull(Collection<T> collection) {
return collection != null ? collection.stream() : Stream.empty();
}
}
Loading

0 comments on commit 7d1707a

Please sign in to comment.