Skip to content

Commit

Permalink
feat: implement token endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
borisrizov-zf committed Feb 9, 2024
1 parent e982919 commit 4227f3e
Show file tree
Hide file tree
Showing 10 changed files with 670 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ******************************************************************************
*/

package org.eclipse.tractusx.managedidentitywallets.apidocs;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class SecureTokenControllerApiDoc {

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@RequestBody(content = {
@Content(examples = {
@ExampleObject(name = "Request Secure Token using Scopes", value = """
{
"audience": "BPNL000000000009",
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"grant_type": "client_credentials",
"bearer_access_scope": "org.eclipse.tractusx.vc.type:ValidCredentialType:read"
}
"""
),
@ExampleObject(name = "Request Secure Token using Access Token", value = """
{
"audience": "BPNL000000000009",
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"grant_type": "client_credentials",
"access_token": "a_jwt_token"
}
"""
)
})
})
@ApiResponses(value = {
@ApiResponse(responseCode = "201", content = {
@Content(examples = {
@ExampleObject(name = "Success response", value = """
{
"token": "a_jwt_token",
"expiresAt": 1706888709315
}
"""
)
})
}),

@ApiResponse(responseCode = "400", content = {
@Content(examples = {
@ExampleObject(name = "Unknown BPN", value = """
{
"error": "UnknownBusinessPartnerNumber",
"errorDescription": "The provided BPN 'BPNL000000000001' is unknown"
}
"""
),

@ExampleObject(name = "Wrong Grant Type", value = """
{
"error": "UnsupportedGrantTypeException",
"errorDescription": "The provided 'grant_type' is not valid. Use 'client_credentials'."
}
"""
),

@ExampleObject(name = "Invalid Secure Token Request", value = """
{
"error": "InvalidSecureTokenRequest",
"errorDescription": "The provided data could not be used to create and sign a token."
}
"""
)
})
}),

@ApiResponse(responseCode = "500", description = "Any other internal server error", content = {
@Content(examples = {
@ExampleObject(name = "Internal server error", value = """
{
"type": "about:blank",
"title": "Error Title",
"status": 500,
"detail": "Error Details",
"instance": "API endpoint",
"properties": {
"timestamp": 1689762476720
}
}
"""
)
})
})
})
@Operation(summary = "Create and Sign Access Tokens", description = "The endpoint for creating and signing access tokens which are to be used during a verifiable presentation flow.")
public @interface PostSecureTokenDoc {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,15 @@ public class SecurityConfig {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.xssProtection(Customizer.withDefaults()).contentSecurityPolicy(contentSecurityPolicyConfig -> contentSecurityPolicyConfig.policyDirectives("script-src 'self'")))
.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer
.xssProtection(Customizer.withDefaults())
.contentSecurityPolicy(contentSecurityPolicyConfig -> contentSecurityPolicyConfig.policyDirectives("script-src 'self'")))
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.requestMatchers(new AntPathRequestMatcher("/")).permitAll() // forwards to swagger
.requestMatchers(new AntPathRequestMatcher("/docs/api-docs/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/ui/swagger-ui/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/actuator/health/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/token", POST.name())).permitAll()
.requestMatchers(new AntPathRequestMatcher("/actuator/loggers/**")).hasRole(ApplicationRole.ROLE_MANAGE_APP)

//did document resolve APIs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ******************************************************************************
*/

package org.eclipse.tractusx.managedidentitywallets.controller;

import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.tractusx.managedidentitywallets.apidocs.SecureTokenControllerApiDoc;
import org.eclipse.tractusx.managedidentitywallets.domain.BusinessPartnerNumber;
import org.eclipse.tractusx.managedidentitywallets.domain.IdpTokenResponse;
import org.eclipse.tractusx.managedidentitywallets.domain.StsTokenErrorResponse;
import org.eclipse.tractusx.managedidentitywallets.domain.StsTokenResponse;
import org.eclipse.tractusx.managedidentitywallets.dto.SecureTokenRequest;
import org.eclipse.tractusx.managedidentitywallets.exception.InvalidSecureTokenRequest;
import org.eclipse.tractusx.managedidentitywallets.exception.UnknownBusinessPartnerNumber;
import org.eclipse.tractusx.managedidentitywallets.exception.UnsupportedGrantTypeException;
import org.eclipse.tractusx.managedidentitywallets.interfaces.SecureTokenService;
import org.eclipse.tractusx.managedidentitywallets.service.IdpAuthorization;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Set;

@RestController
@Slf4j
@RequiredArgsConstructor
@Tag(name = "STS")
public class SecureTokenController {

private final SecureTokenService tokenService;

private final IdpAuthorization idpAuthorization;

@SneakyThrows
@PostMapping(path = "/token", consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
@SecureTokenControllerApiDoc.PostSecureTokenDoc
public ResponseEntity<StsTokenResponse> store(
@Valid @RequestBody SecureTokenRequest secureTokenRequest
) {
// handle idp authorization
IdpTokenResponse idpResponse = idpAuthorization.fromSecureTokenRequest(secureTokenRequest);
BusinessPartnerNumber bpn = idpResponse.bpn();
// todo bri: accept did & bpn
BusinessPartnerNumber partnerBpn = new BusinessPartnerNumber(secureTokenRequest.getAudience());

// create the SI token and put/create the access_token inside
JWT responseJwt;
if (secureTokenRequest.assertValidWithAccessToken()) {
log.debug("Signing si token.");
responseJwt = tokenService.issueToken(
bpn,
partnerBpn,
JWTParser.parse(secureTokenRequest.getAccessToken())
);
} else if (secureTokenRequest.assertValidWithScopes()) {
log.debug("Creating access token and signing si token.");
responseJwt = tokenService.issueToken(
bpn,
partnerBpn,
Set.of(secureTokenRequest.getBearerAccessScope())
);
} else {
throw new InvalidSecureTokenRequest("The provided data could not be used to create and sign a token.");
}

// create the response
log.debug("Preparing StsTokenResponse.");
StsTokenResponse response = StsTokenResponse.builder()
.token(responseJwt.serialize())
.expiresAt(responseJwt.getJWTClaimsSet().getExpirationTime().getTime())
.build();
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@ExceptionHandler({ UnsupportedGrantTypeException.class, InvalidSecureTokenRequest.class, UnknownBusinessPartnerNumber.class })
public ResponseEntity<StsTokenErrorResponse> getErrorResponse(RuntimeException e) {
StsTokenErrorResponse response = new StsTokenErrorResponse();
response.setError(e.getClass().getSimpleName());
response.setErrorDescription(e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,20 @@


import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.*;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Transient;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.tractusx.managedidentitywallets.utils.StringToDidDocumentConverter;
import org.eclipse.tractusx.ssi.lib.model.did.DidDocument;
import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ******************************************************************************
*/

package org.eclipse.tractusx.managedidentitywallets.interfaces;

import com.nimbusds.jwt.JWT;
import org.eclipse.tractusx.managedidentitywallets.domain.DID;
import org.eclipse.tractusx.managedidentitywallets.domain.KeyPair;

import java.time.Instant;
import java.util.Set;

public interface SecureTokenIssuer {
JWT createAccessToken(KeyPair keyPair, DID self, DID partner, Instant expirationTime, Set<String> scopes);

JWT createIdToken(KeyPair keyPair, DID self, DID partner, Instant expirationTime, JWT accessToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ******************************************************************************
*/

package org.eclipse.tractusx.managedidentitywallets.interfaces;

import org.eclipse.tractusx.managedidentitywallets.domain.BusinessPartnerNumber;
import org.eclipse.tractusx.managedidentitywallets.domain.DID;

import com.nimbusds.jwt.JWT;

import java.util.Set;

public interface SecureTokenService {
JWT issueToken(DID self, DID partner, Set<String> scopes);

JWT issueToken(BusinessPartnerNumber self, BusinessPartnerNumber partner, Set<String> scopes);

JWT issueToken(DID self, DID partner, JWT accessToken);

JWT issueToken(BusinessPartnerNumber self, BusinessPartnerNumber partner, JWT accessToken);
}
Loading

0 comments on commit 4227f3e

Please sign in to comment.