Skip to content

Commit 0e16c6e

Browse files
authored
Merge pull request #67 from FIWARE/token-exchang
Token exchange
2 parents a255a4a + 68b4726 commit 0e16c6e

23 files changed

Lines changed: 811 additions & 258 deletions

.github/workflows/test.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Set up Go
1414
uses: actions/setup-go@v2
1515
with:
16-
go-version: 1.21
16+
go-version: 1.24
1717

1818
- name: Install coveralls dependencies
1919
run: |
@@ -25,6 +25,7 @@ jobs:
2525
run: |
2626
go get github.com/gookit/goutil@v0.6.6
2727
go get github.com/gookit/goutil/envutil@v0.6.6
28+
go get github.com/gin-gonic/gin@v1.9.1
2829
2930
- name: Go test
3031
run: |

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.23-alpine AS build
1+
FROM golang:1.24-alpine AS build
22

33
WORKDIR /go/src/app
44
COPY ./ ./
@@ -8,7 +8,7 @@ RUN apk add build-base
88
RUN go get -d -v ./...
99
RUN go build -v .
1010

11-
FROM golang:1.23-alpine
11+
FROM golang:1.24-alpine
1212

1313
LABEL org.opencontainers.image.source="https://github.com/FIWARE/VCVerifier"
1414

api/api.yaml

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ tags:
1010
description: All api-endpoints, e.g. the once that can be reused by other applications
1111

1212
paths:
13+
/api/v1/authorization:
14+
get:
15+
tags:
16+
- api
17+
parameters:
18+
- $ref: '#/components/parameters/QueryState'
19+
- $ref: '#/components/parameters/ClientId'
20+
- $ref: '#/components/parameters/RedirectUri'
21+
- $ref: '#/components/parameters/RequestUri'
22+
- $ref: '#/components/parameters/Scope'
23+
- $ref: '#/components/parameters/Nonce'
24+
- $ref: '#/components/parameters/ResponseType'
25+
operationId: AuthorizationEndpoint
26+
summary: OAuth-2 compliant authorization endpoint, to provide the correct redirect
27+
description: Endpoint to be used as entry for the authorization process, providing the redirect to the concrete authorization method.
28+
In case a request_uri provides access to an request object, all mandatory parameters can also be provided as part of that object.
29+
responses:
30+
'302':
31+
description: A redirect to the authorization entrypoint.
32+
1333
/api/v2/loginQR:
1434
get:
1535
tags:
@@ -97,6 +117,9 @@ paths:
97117
parameters:
98118
- $ref: '#/components/parameters/QueryState'
99119
- $ref: '#/components/parameters/ClientId'
120+
- $ref: '#/components/parameters/Scope'
121+
- $ref: '#/components/parameters/RequestMode'
122+
- $ref: '#/components/parameters/RedirectPath'
100123
operationId: StartSIOPSameDevice
101124
summary: Starts the siop flow for credentials hold by the same device
102125
description: When the credential is already present in the requesting browser, the same-device flow can be used. It creates the login information and then redirects to the /authenticationresponse path.
@@ -153,7 +176,6 @@ paths:
153176
responses:
154177
'204':
155178
description: Ok when it worked
156-
157179
/token:
158180
post:
159181
tags:
@@ -302,6 +324,14 @@ components:
302324
schema:
303325
type: string
304326
example: https://my-app.com/request.jwt
327+
RedirectPath:
328+
name: redirect_path
329+
description: If no redirect path is provided, an 'oid4vp' deeplink will be returned
330+
in: query
331+
required: false
332+
schema:
333+
type: string
334+
example: /
305335
VpToken:
306336
name: vp_token
307337
description: base64URLEncoded VerifiablePresentation
@@ -344,6 +374,14 @@ components:
344374
schema:
345375
type: string
346376
example: packet-delivery-portal
377+
ResponseType:
378+
name: response_type
379+
in: query
380+
required: false
381+
description: Type of the expected response. Currently, only "code" is supported
382+
schema:
383+
type: string
384+
enum: ["code"]
347385
schemas:
348386
CredentialsType:
349387
type: array
@@ -600,7 +638,7 @@ components:
600638
properties:
601639
grant_type:
602640
type: string
603-
enum: ["authorization_code"]
641+
enum: ["authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange"]
604642
code:
605643
type: string
606644
example: myRandomString
@@ -609,15 +647,43 @@ components:
609647
format: uri
610648
description: Same uri as provided as callback in the original request.
611649
example: https://my-portal.com/auth_callback
650+
resource:
651+
type: string
652+
format: uri
653+
description: A URI that indicates the target service or resource where the client intends to use the requested security token. Resource
654+
is ignored if the target client is provided as path parameter
655+
audience:
656+
type: string
657+
description: The logical name of the target service where the client intends to use the requested security token.
658+
scope:
659+
type: array
660+
items:
661+
type: string
662+
description: A list of space-delimited, case-sensitive strings, that allow the client to specify the desired scope of the requested security token in the context of the service or resource where the token will be used.
663+
requested_token_type:
664+
type: string
665+
description: An identifier, for the type of the requested security token.
666+
enum: ["urn:ietf:params:oauth:token-type:access_token"]
667+
subject_token:
668+
type: string
669+
description: A security token that represents the identity of the party on behalf of whom the request is being made.
670+
subject_token_type:
671+
type: string
672+
description: An identifier that indicates the type of the security token in the subject_token parameter.
673+
enum: ["urn:eu:oidf:vp_token"]
674+
required:
675+
- grant_type
612676
TokenResponse:
613677
type: object
614678
properties:
615679
token_type:
616680
type: string
617681
enum: ["Bearer"]
682+
issued_token_type:
683+
type: string
684+
enum: ["urn:ietf:params:oauth:token-type:access_token"]
618685
expires_in:
619686
type: number
620687
example: 3600
621688
access_token:
622689
type: string
623-

common/metadata.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package common
22

33
const TYPE_CODE = "authorization_code"
44
const TYPE_VP_TOKEN = "vp_token"
5+
const TYPE_TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"
6+
const TYPE_VP_TOKEN_SUBJECT = "urn:eu:oidf:vp_token"
7+
const TYPE_ACCESS_TOKEN = "urn:ietf:params:oauth:token-type:access_token"
58

69
type OpenIDProviderMetadata struct {
710
Issuer string `json:"issuer"`

config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ type Verifier struct {
7373
// policies that shall be checked
7474
PolicyConfig Policies `mapstructure:"policies"`
7575
// path of the authorizationEndpoint to be provided in the .well-known/openid-configuration
76-
AuthorizationEndpoint string `mapstructure:"authorizationEndpoint" default:"/api/v2/loginQR"`
76+
AuthorizationEndpoint string `mapstructure:"authorizationEndpoint"`
7777
// Validation mode for validating the vcs. Does not touch verification, just content validation.
7878
// applicable modes:
7979
// * `none`: No validation, just swallow everything

config/configClient.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ type ServicesResponse struct {
4040

4141
type ConfiguredService struct {
4242
// Default OIDC scope to be used if none is specified
43-
DefaultOidcScope string `json:"defaultOidcScope" mapstructure:"defaultOidcScope"`
44-
ServiceScopes map[string]ScopeEntry `json:"oidcScopes" mapstructure:"oidcScopes"`
45-
Id string `json:"id" mapstructure:"id"`
43+
DefaultOidcScope string `json:"defaultOidcScope" mapstructure:"defaultOidcScope"`
44+
ServiceScopes map[string]ScopeEntry `json:"oidcScopes" mapstructure:"oidcScopes"`
45+
Id string `json:"id" mapstructure:"id"`
46+
AuthorizationType string `json:"authorizationType,omitempty" mapstructure:"authorizationType,omitempty"`
47+
AuthorizationPath string `json:"authorizationPath,omitempty" mapstructure:"authorizationPath,omitempty"`
4648
}
4749

4850
type ScopeEntry struct {
@@ -51,7 +53,7 @@ type ScopeEntry struct {
5153
// Proofs to be requested - see https://identity.foundation/presentation-exchange/#presentation-definition
5254
PresentationDefinition *PresentationDefinition `json:"presentationDefinition" mapstructure:"presentationDefinition"`
5355
// JSON encoded query to request the credentials to be included in the presentation
54-
DCQL *DCQL `json:"dcql,omitempty" mapstructure:"dcql,omitempty"`
56+
DCQL *DCQL `json:"dcql" mapstructure:"dcql"`
5557
// When set, the claim are flatten to plain JWT-claims before beeing included, instead of keeping the credential/presentation structure, where the claims are under the key vc or vp
5658
FlatClaims bool `json:"flatClaims" mapstructure:"flatClaims"`
5759
}
@@ -155,7 +157,7 @@ type CredentialQuery struct {
155157
// A string that specifies the format of the requested Credential.
156158
Format string `json:"format,omitempty" mapstructure:"format,omitempty"`
157159
// A boolean which indicates whether multiple Credentials can be returned for this Credential Query. If omitted, the default value is false.
158-
Multiple bool `json:"multiple,omitempty" mapstructure:"multiple,omitempty"`
160+
Multiple bool `json:"multiple" mapstructure:"multiple"`
159161
// A non-empty array of objects that specifies claims in the requested Credential. Verifiers MUST NOT point to the same claim more than once in a single query. Wallets SHOULD ignore such duplicate claim queries.
160162
Claims []ClaimsQuery `json:"claims,omitempty" mapstructure:"claims,omitempty"`
161163
// Defines additional properties requested by the Verifier that apply to the metadata and validity data of the Credential. The properties of this object are defined per Credential Format. If empty, no specific constraints are placed on the metadata or validity of the requested Credential.

config/configClient_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package config
22

33
import (
44
"io"
5-
"io/ioutil"
5+
"os"
66
"strings"
77
"testing"
88

@@ -20,7 +20,7 @@ func (mhc MockHttpClient) Get(url string) (resp *http.Response, err error) {
2020
}
2121

2222
func readFile(filename string, t *testing.T) string {
23-
data, err := ioutil.ReadFile("data/" + filename)
23+
data, err := os.ReadFile("data/" + filename)
2424
if err != nil {
2525
t.Error("could not read file", err)
2626
}

config/data/config_test.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ server:
55

66
logging:
77
level: "DEBUG"
8-
jsonLogging: "true"
9-
logRequests: "true"
8+
jsonLogging: true
9+
logRequests: true
1010
pathsToSkip: [/health]
1111

1212
verifier:
1313
did: "did:key:somekey"
1414
tirAddress: "https://test.dev/trusted_issuer/v3/issuers/"
1515
sessionExpiry: 30
16+
authorizationEndpoint: "/api/v2/loginQR"
1617
policies:
1718
default:
1819
SignaturePolicy: {}

config/data/test.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
server:
2+
host: http://localhost:8080
3+
port: 3000
4+
staticDir: views/static
5+
templateDir: views/
6+
m2m:
7+
authEnabled: false
8+
logging:
9+
level: DEBUG
10+
logRequests: true
11+
pathsToSkip:
12+
- /metrics
13+
- /health
14+
verifier:
15+
did: did:key:somekey
16+
tirAddress: https://test.dev/trusted_issuer/v3/issuers/
17+
supportedModes:
18+
- byValue
19+
- byReference
20+
configRepo:
21+
services:
22+
- id: test-service-sd
23+
defaultOidcScope: sd-test
24+
authorizationType: "DEEPLINK"
25+
authorizationPath: "/test-service-sd/authorization"
26+
oidcScopes:
27+
"sd-test":
28+
credentials:
29+
- type: LegalPersonCredential
30+
trustedParticipantsLists:
31+
- type: ebsi
32+
url: tir.org
33+
trustedIssuersLists:
34+
- til.org
35+
dcql:
36+
credentials:
37+
- id: legal-person-query
38+
format: dc+sd-jwt
39+
meta:
40+
vct_values:
41+
- LegalPersonCredential

config/provider.go

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,77 @@
11
package config
22

33
import (
4+
"fmt"
5+
"reflect"
6+
47
"github.com/gookit/config/v2"
58
"github.com/gookit/config/v2/yaml"
9+
"github.com/mitchellh/mapstructure"
610
)
711

812
// read the config from the config file
913
func ReadConfig(configFile string) (configuration Configuration, err error) {
1014
config.WithOptions(config.ParseDefault)
1115
config.AddDriver(yaml.Driver)
12-
err = config.LoadFiles(configFile)
1316

17+
if err = config.LoadFiles(configFile); err != nil {
18+
return
19+
}
20+
21+
// pass 1: apply defaults & env vars
22+
if err = config.BindStruct("", &configuration); err != nil {
23+
return
24+
}
25+
26+
raw := config.Data()
27+
normalized := forceStringKeys(raw)
28+
29+
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
30+
TagName: "mapstructure",
31+
Result: &configuration,
32+
})
1433
if err != nil {
1534
return
1635
}
17-
config.BindStruct("", &configuration)
36+
if err = decoder.Decode(normalized); err != nil {
37+
return
38+
}
39+
1840
return
1941
}
42+
43+
func forceStringKeys(m interface{}) interface{} {
44+
switch v := m.(type) {
45+
case map[interface{}]interface{}:
46+
newMap := make(map[string]interface{})
47+
for key, val := range v {
48+
newMap[fmt.Sprintf("%v", key)] = forceStringKeys(val)
49+
}
50+
return newMap
51+
case map[string]interface{}:
52+
newMap := make(map[string]interface{})
53+
for key, val := range v {
54+
newMap[key] = forceStringKeys(val)
55+
}
56+
return newMap
57+
case []interface{}:
58+
for i := range v {
59+
v[i] = forceStringKeys(v[i])
60+
}
61+
return v
62+
default:
63+
return v
64+
}
65+
}
66+
67+
func autoAllocHook() mapstructure.DecodeHookFunc {
68+
return func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
69+
// If target type is a pointer to struct, and source is a map,
70+
// allocate the target before decoding.
71+
if to.Kind() == reflect.Ptr && to.Elem().Kind() == reflect.Struct {
72+
v := reflect.New(to.Elem())
73+
return v.Interface(), nil
74+
}
75+
return data, nil
76+
}
77+
}

0 commit comments

Comments
 (0)