Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/token manager #540

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public ResponseEntity<Void> deleteAllAssetLinksById(@Parameter(in = ParameterIn.

public ResponseEntity<PagedResult> getAllAssetAdministrationShellIdsByAssetLink(@Parameter(in = ParameterIn.QUERY, description = "A list of specific Asset identifiers. Each Asset identifier is a base64-url-encoded [SpecificAssetId](https://api.swaggerhub.com/domains/Plattform_i40/Part1-MetaModel-Schemas/V3.0.1#/components/schemas/SpecificAssetId)" ,schema=@Schema()) @Valid @RequestParam(value = "assetIds", required = false) List<Base64UrlEncodedIdentifier> assetIds,@Min(1)@Parameter(in = ParameterIn.QUERY, description = "The maximum number of elements in the response array" ,schema=@Schema(allowableValues={ "1" }, minimum="1"
)) @Valid @RequestParam(value = "limit", required = false) Integer limit,@Parameter(in = ParameterIn.QUERY, description = "A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue" ,schema=@Schema()) @Valid @RequestParam(value = "cursor", required = false) String cursor) {

System.out.println("Reached Here");
if (limit == null)
limit = 100;

Expand Down Expand Up @@ -108,7 +108,8 @@ public ResponseEntity<List<SpecificAssetId>> getAllAssetLinksById(@Parameter(in
}

public ResponseEntity<List<SpecificAssetId>> postAllAssetLinksById(@Parameter(in = ParameterIn.PATH, description = "The Asset Administration Shell’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("aasIdentifier") Base64UrlEncodedIdentifier aasIdentifier,@Parameter(in = ParameterIn.DEFAULT, description = "A list of specific Asset identifiers", required=true, schema=@Schema()) @Valid @RequestBody List<SpecificAssetId> body) {
List<SpecificAssetId> assetIDs = aasDiscoveryService.createAllAssetLinksById(aasIdentifier.getIdentifier(), body);
System.out.println("Reached Here");
List<SpecificAssetId> assetIDs = aasDiscoveryService.createAllAssetLinksById(aasIdentifier.getIdentifier(), body);

return new ResponseEntity<List<SpecificAssetId>>(assetIDs, HttpStatus.CREATED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ spring.application.name=AAS Discovery Service
basyx.aasdiscoveryservice.name=aas-discovery-service

basyx.backend=InMemory
management.endpoints.web.exposure.include=mappings

#basyx.backend=MongoDB
#spring.data.mongodb.host=127.0.0.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,24 @@
import com.nimbusds.oauth2.sdk.token.AccessToken;
import com.nimbusds.oauth2.sdk.token.RefreshToken;

import net.minidev.json.JSONObject;

/**
* Requests and manages the Access Tokens and Refresh Tokens.
*
* @author danish
* @author danish
*/
public class TokenManager {

private String tokenEndpoint;
private AccessTokenProvider accessTokenProvider;

private static final String REFRESH_EXPIRES_IN = "refresh_expires_in";
private final String tokenEndpoint;
private final AccessTokenProvider accessTokenProvider;
private String accessToken;
private String refreshToken;
private long accessTokenExpiryTime;
private long refreshTokenExpiryTime;
private String refreshToken;
private long accessTokenExpiryTime;
private long refreshTokenExpiryTime;

public TokenManager(String tokenEndpoint, AccessTokenProvider accessTokenProvider) {
super();
this.tokenEndpoint = tokenEndpoint;
this.accessTokenProvider = accessTokenProvider;
}
Expand All @@ -61,46 +63,83 @@ public String getTokenEndpoint() {
public AccessTokenProvider getAccessTokenProvider() {
return this.accessTokenProvider;
}

/**
* Provides access token
* Provides the access token, refreshing it if necessary.
*
* @return accessToken
* @return the current valid access token
* @throws IOException
* if an error occurs while retrieving the token
*/
public synchronized String getAccessToken() throws IOException {
long currentTimeMillis = System.currentTimeMillis();

if (accessToken != null && System.currentTimeMillis() < accessTokenExpiryTime)
return accessToken;
if (accessToken != null && currentTimeMillis < accessTokenExpiryTime) {
return accessToken;
}

if (refreshToken != null && System.currentTimeMillis() < refreshTokenExpiryTime) {
try {
return requestAccessToken(accessTokenProvider.getAccessTokenResponse(tokenEndpoint, refreshToken));
if (refreshToken != null && currentTimeMillis < refreshTokenExpiryTime) {
try {
return updateTokens(accessTokenProvider.getAccessTokenResponse(tokenEndpoint, refreshToken), currentTimeMillis);
} catch (IOException e) {
throw new AccessTokenRetrievalException("Error occurred while retrieving access token" + e.getMessage());
throw new AccessTokenRetrievalException("Error occurred while retrieving access token: " + e.getMessage());
}
}
}

try {
return requestAccessToken(accessTokenProvider.getAccessTokenResponse(tokenEndpoint));
try {
return updateTokens(accessTokenProvider.getAccessTokenResponse(tokenEndpoint), currentTimeMillis);
} catch (IOException e) {
throw new AccessTokenRetrievalException("Error occurred while retrieving access token" + e.getMessage());
throw new AccessTokenRetrievalException("Error occurred while retrieving access token: " + e.getMessage());
}
}

private String requestAccessToken(AccessTokenResponse accessTokenResponse) throws IOException {
AccessToken accessTokenObj = accessTokenResponse.getTokens().getAccessToken();
accessToken = accessTokenObj.getValue();
accessTokenExpiryTime = accessTokenObj.getLifetime();

RefreshToken refreshTokenObj = accessTokenResponse.getTokens().getRefreshToken();

if (refreshTokenObj != null) {
refreshToken = refreshTokenObj.getValue();
refreshTokenExpiryTime = System.currentTimeMillis() + (30L * 24L * 60L * 60L * 1000L);
}

return accessToken;
}

}
}

/**
* Updates the tokens and their expiry times.
*
* @param accessTokenResponse
* the response containing the new tokens
* @param currentTimeMillis
* the current timestamp in milliseconds for consistency
* @return the new access token
* @throws IOException
* if an error occurs while processing the response
*/
private String updateTokens(AccessTokenResponse accessTokenResponse, long currentTimeMillis) throws IOException {
AccessToken accessTokenObj = accessTokenResponse.getTokens().getAccessToken();
accessToken = accessTokenObj.getValue();
accessTokenExpiryTime = currentTimeMillis + convertToMilliseconds(accessTokenObj.getLifetime());

RefreshToken refreshTokenObj = accessTokenResponse.getTokens().getRefreshToken();

if (refreshTokenObj != null) {
refreshToken = refreshTokenObj.getValue();
refreshTokenExpiryTime = calculateRefreshTokenExpiry(accessTokenResponse, currentTimeMillis);
}

return accessToken;
}

/**
* Extracts the refresh token's expiry time from the response.
*
* @param accessTokenResponse
* the response containing the refresh token
* @param currentTimeMillis
* the current timestamp in milliseconds for consistency
* @return the expiry time in epoch millis, or 0 if not available
*/
private long calculateRefreshTokenExpiry(AccessTokenResponse accessTokenResponse, long currentTimeMillis) {
JSONObject jsonObject = accessTokenResponse.toJSONObject();
Number refreshExpiresInSeconds = jsonObject.getAsNumber(REFRESH_EXPIRES_IN);

if (refreshExpiresInSeconds == null) {
return 0;
}

return currentTimeMillis + convertToMilliseconds(refreshExpiresInSeconds.longValue());
}

private long convertToMilliseconds(long refreshExpiresInSeconds) {
return refreshExpiresInSeconds * 1000L;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public DescriptionController(List<ProfileDeclaration> declarations) {
}
}

@Operation(operationId = "getDescription",
@Operation(operationId = "getDescriptions",
summary = "Returns the self-describing information of a network resource (ServiceDescription)",
tags = {"Registry and Discovery Interface"}, responses = {
@ApiResponse(responseCode = "200", description = "Requested Description", content = {
Expand All @@ -43,8 +43,8 @@ public DescriptionController(List<ProfileDeclaration> declarations) {
content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))}),
@ApiResponse(responseCode = "default", description = "Default error handling for unmentioned status codes",
content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))})})
@RequestMapping(method = RequestMethod.GET, value = "/description", produces = {"application/json"})
public ResponseEntity<ServiceDescription> getDescription() {
@RequestMapping(method = RequestMethod.GET, value = "/descriptions", produces = {"application/json"})
public ResponseEntity<ServiceDescription> getDescriptions() {
ServiceDescription serviceDescription = new ServiceDescription();
serviceDescription.profiles(new ArrayList<>(profiles));
return new ResponseEntity<>(serviceDescription, HttpStatus.OK);
Expand Down
69 changes: 69 additions & 0 deletions basyx.digitaltwinregistry/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Eclipse BaSyx - AAS Environment
Eclipse BaSyx provides the AAS Environment as off-the-shelf component:

docker run --name=aas-env -p:8081:8081 -v C:/tmp/application.properties:/application/application.properties eclipsebasyx/aas-environment:2.0.0-SNAPSHOT

> *Disclaimer*: In this example, configuration files are located in `C:/tmp`

> *Disclaimer*: The binding of volume `C:/tmp/application.properties` to `/application/application.properties` is tested using Windows Powershell. Other terminals might run into an error.

It aggregates the AAS Repository, Submodel Repository and ConceptDescription Repository into a single component. For its features and configuration, see the documentation of the respective components.

In addition, it supports the following endpoint defined in DotAAS Part 2 V3 - Serialization Interface:
- GenerateSerializationByIds. For more information about this endpoint please refer to [Swagger API](https://app.swaggerhub.com/apis/Plattform_i40/Entire-API-Collection/V3.0.1#/Serialization%20API/GenerateSerializationByIds)

The Aggregated API endpoint documentation is available at:

http://{host}:{port}/v3/api-docs

The Aggregated Swagger UI for the endpoint is available at:

http://{host}:{port}/swagger-ui/index.html

For a configuration example, see [application.properties](./basyx.aasenvironment.component/src/main/resources/application.properties)
The Health Endpoint and CORS Documentation can be found [here](../docs/Readme.md).

## Preconfiguration of AAS Environments
The AAS Environment Component supports the preconfiguration of AAS Environments (e.g., XML, JSON, AASX) via the _basyx.environment_ parameter.

The feature supports both preconfiguring explicit files (e.g., file:myDevice.aasx) as well as directories (e.g., file:myDirectory) that will be recursively scanned for serialized environments.

Please note that collision of ids of Submodels and AAS in the preconfigured environments will lead to an error. For ConceptDescriptions, however, id collisions are ignored since they are assumed to be identical. Thus, only the first occurance of a ConceptDescription with the same Id will be uploaded. Further ConceptDescriptions with the same Id will only lead to a warning in the log.

Furthermore, if Identifiables (AAS, Submodels, ConceptDescriptions) are already existing in the repositories before adding the preconfigured environments (e.g., due to using MongoDB persistency and restarting the server), the _Version_ & _Revision_ (cf. AdministrativeInformation) are leveraged for determining if the existing Identifiables should be overwritten. The following examples illustrate this behavior:
* Preconfigured Identifiable has same version and same revision in comparison to the already existing => No overwriting
* Preconfigured Identifiable has older version or same version and older revision in comparison to the already existing => No overwriting
* Preconfigured Identifiable has newer version or same version but newer revision in comparison to the already existing => Server version is overwritten


For examples, see [application.properties](./basyx.aasenvironment.component/src/main/resources/application.properties)

## AAS Environment Upload Endpoint

AAS environments (e.g. XML, JSON, AASX) can be uploaded by a multipart/form-data POST on the `/upload` endpoint. Please note that the following MIME types as **Accept Header** are expected for the respective file uploads:
* AASX: application/asset-administration-shell-package
* JSON: application/json
* XML: application/xml

Below is an example curl request:

```
curl --location 'http://localhost:8081/upload' \
--header 'Accept: application/asset-administration-shell-package' \
--form 'file=@"Sample.aasx"'

```

The upload follows the same rules as the preconfiguration in terms of handling existing AAS, submodels and concept descriptions. In order for the file to be recognized correctly, please make sure that its MIME type is properly configured.

**Note**
If the AAS Environment file (XML, JSON, or AASX) size exceeds the below mentioned default limit, it is important to set the below two properties in the application.properties based on the size of the file to be uploaded:

spring.servlet.multipart.max-file-size (default 1 MB)
spring.servlet.multipart.max-request-size (default 10 MB)

## AAS Environment Features
* [AAS Environment Authorization](basyx.aasenvironment-feature-authorization)

## Configure Favicon
To configure the favicon, add the favicon.ico to [basyx-java-server-sdk\basyx.common\basyx.http\src\main\resources\static](../basyx.common/basyx.http/src/main/resources/static/).
81 changes: 81 additions & 0 deletions basyx.digitaltwinregistry/basyx.digitaltwinregistry-http/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.digitaltwinregistry</artifactId>
<version>${revision}</version>
</parent>

<artifactId>basyx.digitaltwinregistry-http</artifactId>
<name>BaSyx AAS Environment HTTP</name>
<description>BaSyx AAS Environment HTTP</description>

<dependencies>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.http</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.aasdiscoveryservice-http</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.aasregistry-service</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.http</artifactId>
<scope>test</scope>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.aas4j</groupId>
<artifactId>aas4j-dataformat-json</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*******************************************************************************
* Copyright (C) 2023 the Eclipse BaSyx Authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* SPDX-License-Identifier: MIT
******************************************************************************/


package org.eclipse.digitaltwin.basyx.digitaltwinregistry.http;

import org.eclipse.digitaltwin.basyx.http.CorsPathPatternProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
*
* @author schnicke
*
*/
@Configuration
public class AASEnvironmentConfiguration {

@Bean
public CorsPathPatternProvider getAASEnvironmentSerializationRepoCorsUrlProvider() {
return new CorsPathPatternProvider("/serialization");
}

@Bean
public CorsPathPatternProvider getAASEnvironmentUploadRepoCorsUrlProvider() {
return new CorsPathPatternProvider("/upload");
}
}
Loading
Loading