Skip to content
Snippets Groups Projects
Commit 75b7edaa authored by Denis Sukhoroslov's avatar Denis Sukhoroslov
Browse files

Merge branch 'main' into 'main'

Added documentation in various places

See merge request eclipse/xfsc/authenticationauthorization!222
parents b8d81a87 3eafd9d6
Branches main
No related tags found
No related merge requests found
## Claims Information Point
AAS implements CIP in order to provide an ability to resolve permissions in IAM (keycloak) dynamically. This is done with help of keycloak [Policy Enforcers](http://www.keycloak.org/docs/latest/authorization_services/index.html#_enforcer_overview) functionality.
\ No newline at end of file
The AAS implements the CIP in order to provide an ability to resolve permissions in IAM (keycloak) dynamically.
In the reference implementation keycloak [Policy Enforcers](http://www.keycloak.org/docs/latest/authorization_services/index.html#_enforcer_overview) functionality are used for authorization.
The CIP is found at `GET` `/cip/claims` and takes two query parameters:
- `sub` - the subject or username for which to get the claims
- `scope` - a whitespace seperated list of scopes, i.e. `openid email profile`. Defaults to `openid`.
The endpoint returns a JSON object with the keys being the claim types and the values being the claims.
For example
```json
{
'sub': 'user123',
'preferred_username': 'user1',
'name': 'John',
'family_name': 'Doe',
'company': 'John's Company Ltd.',
'email': 'john@example.org',
}
```
The AAS only forwards user claims it gets from the TSA, so the exact claims that are available depend on the TSA.
......@@ -11,6 +11,13 @@ The SSI OIDC login flow. Participants are:
![SSI OIDC login](./images/ssi_ciba_login.png "SSI OIDC login")
Additional information:
- 1.11: The AAS makes a `POST` request to the TSA, for the `GetLoginProofInvitation` policy. For example, it could be to this endpoint, depending on the AAS configuration: `/policy/example/GetLoginProofInvitation/1.0/evaluation`. No parameters are set.
- 1.12: The TSA responds with a JSON object with keys `link`, `requestId` and `status`, the status is `pending` at this point.
- 1.18: The AAS makes a `POST` request to the TSA, for the `GetLoginProofResult` policy. For example, it could be to this endpoint, depending on the AAS configuration: `/policy/example/GetLoginProofResult/1.0/evaluation`. The AAS sends a `requestId` in the post body of the request.
- 1.19: If the login is still pending, the TSA returns a JSON object with `status: pending` and the HTTP response code is 204. If a user has logged in, the return code is 200 and the user claims are sent, together with `status: accepted`.
Data Flow Diagram for SSI OIDC login is:
![OIDC Login DFD](./images/oidc_login_dfd.png "OIDC Login DFD")
\ No newline at end of file
## AAS Installation & Configuration
Deployment scripts were provided to deploy services in Kubernetes environment, see `/service/deploy` folder. AAS Server installation can be started with Service:
Deployment scripts were provided to deploy services in a Kubernetes environment, see `/service/deploy` folder. The AAS Server installation can be started with Service:
```
```shell
>kubectl apply -f service.yaml
```
Then install secrets:
```
```shell
>kubectl apply -f secret-keys.yaml
>kubectl apply -f secret-oidc.yaml
>kubectl apply -f secret-siop.yaml
......@@ -30,11 +30,16 @@ Then install the AAS Deployment and Ingress:
>kubectl apply -f ingress.yaml
```
### Configuration
All service settings are specified in the base `application.yml` file and its profile-specific extensions:
- `application-test.yml`: for basic test environment
- `application-test-suite.yml`: for testing with Conformance Suite in test environment
- `application-prod.yml`: for production environment
You can specify which profile should be active by setting the `SPRING_PROFILES_ACTIVE` environment variable to the profile name (in this case either `test`, `test-suite` or `prod`).
You can find these profile config files in the `/service/src/main/resources` directory in this repository.
Profile-specific settings override what is specified in the base `application.yml` (default) profile. If some value not set in profile-specific file then
default value used.
The full list of AAS properties is:
......@@ -76,6 +81,16 @@ The full list of AAS properties is:
| aas.tsa.statuses.GetIatProofResult | Status to return in response from TSA policy evaluation API for GetIatProofResult policy (used in test profiles only) | ACCEPTED |
### Configuring the metadata database
The AAS uses an SQL database (in the reference setup, this is a PostgrSQL database). The settings to configure are
- `spring.datasource.url` - The URL where the database is reachable, something like: `jdbc:postgresql://postgresql-aas:5432/aas`
- `spring.datasource.username` - The username to access the database
- `spring.datasource.password` - The password to access the database
### Updating configuration settings with environment variables
Any AAS property can be overwritten with environment variable in uppercase form: `aas.iam.base-uri -> AAS_IAM_BASE_URI`, where all separators
(dots, hyphens) are substituted with underscores.
......
package eu.xfsc.aas.client;
import java.time.Instant;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import eu.xfsc.aas.generated.model.AccessRequestStatusDto;
import eu.xfsc.aas.properties.StatusProperties;
import lombok.extern.slf4j.Slf4j;
import static eu.xfsc.aas.generated.model.AccessRequestStatusDto.*;
import static eu.xfsc.aas.model.TrustServicePolicy.*;
@Slf4j
public class LocalTrustServiceClientImpl implements TrustServiceClient {
private final Map<String, Integer> countdowns = new ConcurrentHashMap<>();
private final StatusProperties statusProperties;
@Value("${aas.oidc.issuer}")
private String oidcIssuer;
@Value("${aas.tsa.request.count}")
private int pendingRequestCount;
public LocalTrustServiceClientImpl(StatusProperties statusProperties) {
this.statusProperties = statusProperties;
}
public void setStatusConfig(String policy, AccessRequestStatusDto status) {
statusProperties.setPolicyStatus(policy, status);
}
@Override
public Map<String, Object> evaluate(String policy, Map<String, Object> params) {
Map<String, Object> map = new HashMap<>();
String requestId = (String) params.get(PN_REQUEST_ID);
if (requestId == null && (GET_LOGIN_PROOF_INVITATION.equals(policy) || GET_LOGIN_PROOF_RESULT.equals(policy))) {
requestId = (String) params.get(IdTokenClaimNames.SUB);
}
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
map.put(PN_REQUEST_ID, requestId);
if (GET_IAT_PROOF_INVITATION.equals(policy)) {
map.put(PN_STATUS, PENDING);
return map;
}
if (GET_LOGIN_PROOF_INVITATION.equals(policy)) {
map.put(PN_LINK, LINK_SCHEME + requestId);
return map;
}
if (GET_LOGIN_PROOF_RESULT.equals(policy) || GET_IAT_PROOF_RESULT.equals(policy)) {
if (isPending(requestId)) {
map.put(PN_STATUS, PENDING);
} else {
AccessRequestStatusDto status = statusProperties.getPolicyStatus(policy);
if (status == null) {
status = ACCEPTED;
}
map.put("status", status);
if (GET_LOGIN_PROOF_RESULT.equals(policy)) {
long stamp = System.currentTimeMillis();
map.put(StandardClaimNames.NAME, requestId);
map.put(StandardClaimNames.GIVEN_NAME, requestId + ": " + stamp);
map.put(StandardClaimNames.FAMILY_NAME, String.valueOf(stamp));
map.put(StandardClaimNames.MIDDLE_NAME, "");
map.put(StandardClaimNames.PREFERRED_USERNAME, requestId + " " + stamp);
map.put(StandardClaimNames.GENDER, stamp % 2 == 0 ? "F" : "M");
map.put(StandardClaimNames.BIRTHDATE, LocalDate.now().minusYears(21).toString());
map.put(StandardClaimNames.UPDATED_AT, Instant.now().minusSeconds(86400).getEpochSecond());
map.put(StandardClaimNames.EMAIL, requestId + "@oidc.ssi");
map.put(StandardClaimNames.EMAIL_VERIFIED, Boolean.TRUE);
}
if (status == ACCEPTED) {
map.put(IdTokenClaimNames.AUTH_TIME, Instant.now().getEpochSecond());
}
}
map.put(IdTokenClaimNames.SUB, "urn:id:" + requestId);
map.put(IdTokenClaimNames.ISS, oidcIssuer);
}
log.debug("evaluate.exit; policy: {}, params: {}, result: {} ", policy, params, map);
return map;
}
private synchronized boolean isPending(String requestId) {
int pendingCount = countdowns.getOrDefault(requestId, pendingRequestCount);
if (pendingCount <= 0) {
countdowns.remove(requestId);
return false;
}
countdowns.put(requestId, pendingCount - 1);
return true;
}
}
package eu.xfsc.aas.client;
import java.time.Instant;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import eu.xfsc.aas.generated.model.AccessRequestStatusDto;
import eu.xfsc.aas.properties.StatusProperties;
import lombok.extern.slf4j.Slf4j;
import static eu.xfsc.aas.generated.model.AccessRequestStatusDto.*;
import static eu.xfsc.aas.model.TrustServicePolicy.*;
/**
* This class is a mock implementation of the TSA. It is only usable for testing
* and demonstration purposes, and is enabled if you use the 'test' profile.
*/
@Slf4j
public class LocalTrustServiceClientImpl implements TrustServiceClient {
private final Map<String, Integer> countdowns = new ConcurrentHashMap<>();
private final StatusProperties statusProperties;
@Value("${aas.oidc.issuer}")
private String oidcIssuer;
@Value("${aas.tsa.request.count}")
private int pendingRequestCount;
public LocalTrustServiceClientImpl(StatusProperties statusProperties) {
this.statusProperties = statusProperties;
}
public void setStatusConfig(String policy, AccessRequestStatusDto status) {
statusProperties.setPolicyStatus(policy, status);
}
@Override
public Map<String, Object> evaluate(String policy, Map<String, Object> params) {
Map<String, Object> map = new HashMap<>();
String requestId = (String) params.get(PN_REQUEST_ID);
if (requestId == null && (GET_LOGIN_PROOF_INVITATION.equals(policy) || GET_LOGIN_PROOF_RESULT.equals(policy))) {
requestId = (String) params.get(IdTokenClaimNames.SUB);
}
if (requestId == null) {
requestId = UUID.randomUUID().toString();
}
map.put(PN_REQUEST_ID, requestId);
if (GET_IAT_PROOF_INVITATION.equals(policy)) {
map.put(PN_STATUS, PENDING);
return map;
}
if (GET_LOGIN_PROOF_INVITATION.equals(policy)) {
map.put(PN_LINK, LINK_SCHEME + requestId);
return map;
}
if (GET_LOGIN_PROOF_RESULT.equals(policy) || GET_IAT_PROOF_RESULT.equals(policy)) {
if (isPending(requestId)) {
map.put(PN_STATUS, PENDING);
} else {
AccessRequestStatusDto status = statusProperties.getPolicyStatus(policy);
if (status == null) {
status = ACCEPTED;
}
map.put("status", status);
if (GET_LOGIN_PROOF_RESULT.equals(policy)) {
long stamp = System.currentTimeMillis();
map.put(StandardClaimNames.NAME, requestId);
map.put(StandardClaimNames.GIVEN_NAME, requestId + ": " + stamp);
map.put(StandardClaimNames.FAMILY_NAME, String.valueOf(stamp));
map.put(StandardClaimNames.MIDDLE_NAME, "");
map.put(StandardClaimNames.PREFERRED_USERNAME, requestId + " " + stamp);
map.put(StandardClaimNames.GENDER, stamp % 2 == 0 ? "F" : "M");
map.put(StandardClaimNames.BIRTHDATE, LocalDate.now().minusYears(21).toString());
map.put(StandardClaimNames.UPDATED_AT, Instant.now().minusSeconds(86400).getEpochSecond());
map.put(StandardClaimNames.EMAIL, requestId + "@oidc.ssi");
map.put(StandardClaimNames.EMAIL_VERIFIED, Boolean.TRUE);
}
if (status == ACCEPTED) {
map.put(IdTokenClaimNames.AUTH_TIME, Instant.now().getEpochSecond());
}
}
map.put(IdTokenClaimNames.SUB, "urn:id:" + requestId);
map.put(IdTokenClaimNames.ISS, oidcIssuer);
}
log.debug("evaluate.exit; policy: {}, params: {}, result: {} ", policy, params, map);
return map;
}
private synchronized boolean isPending(String requestId) {
int pendingCount = countdowns.getOrDefault(requestId, pendingRequestCount);
if (pendingCount <= 0) {
countdowns.remove(requestId);
return false;
}
countdowns.put(requestId, pendingCount - 1);
return true;
}
}
......@@ -16,6 +16,9 @@ import org.springframework.web.reactive.function.client.WebClient;
import eu.xfsc.aas.generated.model.AccessRequestStatusDto;
import lombok.extern.slf4j.Slf4j;
/**
* This is the production implementation of the TSA and it is enabled if you use the 'prod' profile.
*/
@Slf4j
public class RestTrustServiceClientImpl implements TrustServiceClient {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment