• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

FIWARE / VCVerifier / 15469695945

05 Jun 2025 02:28PM UTC coverage: 42.598% (-2.8%) from 45.372%
15469695945

Pull #58

github

wistefan
fix more tests
Pull Request #58: Jwt inclusion

58 of 298 new or added lines in 9 files covered. (19.46%)

4 existing lines in 3 files now uncovered.

1243 of 2918 relevant lines covered (42.6%)

0.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.93
/openapi/api_api.go
1
/*
2
 * vcverifier
3
 *
4
 * Backend component to verify credentials
5
 *
6
 * API version: 0.0.1
7
 * Generated by: OpenAPI Generator (https://openapi-generator.tech)
8
 */
9

10
package openapi
11

12
import (
13
        "encoding/base64"
14
        "errors"
15
        "fmt"
16
        "net/http"
17
        "strings"
18

19
        "github.com/fiware/VCVerifier/common"
20
        "github.com/fiware/VCVerifier/logging"
21
        "github.com/fiware/VCVerifier/verifier"
22
        "github.com/trustbloc/vc-go/verifiable"
23

24
        "github.com/gin-gonic/gin"
25
)
26

27
var apiVerifier verifier.Verifier
28
var presentationParser verifier.PresentationParser
29
var sdJwtParser verifier.SdJwtParser
30

31
var ErrorMessagNoGrantType = ErrorMessage{"no_grant_type_provided", "Token requests require a grant_type."}
32
var ErrorMessageUnsupportedGrantType = ErrorMessage{"unsupported_grant_type", "Provided grant_type is not supported by the implementation."}
33
var ErrorMessageNoCode = ErrorMessage{"no_code_provided", "Token requests require a code."}
34
var ErrorMessageNoRedircetUri = ErrorMessage{"no_redirect_uri_provided", "Token requests require a redirect_uri."}
35
var ErrorMessageNoState = ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."}
36
var ErrorMessageNoScope = ErrorMessage{"no_scope_provided", "Authentication requires a scope provided as a form parameter."}
37
var ErrorMessageNoToken = ErrorMessage{"no_token_provided", "Authentication requires a token provided as a form parameter."}
38
var ErrorMessageNoPresentationSubmission = ErrorMessage{"no_presentation_submission_provided", "Authentication requires a presentation submission provided as a form parameter."}
39
var ErrorMessageNoCallback = ErrorMessage{"NoCallbackProvided", "A callback address has to be provided as query-parameter."}
40
var ErrorMessageUnableToDecodeToken = ErrorMessage{"invalid_token", "Token could not be decoded."}
41
var ErrorMessageUnableToDecodeCredential = ErrorMessage{"invalid_token", "Could not read the credential(s) inside the token."}
42
var ErrorMessageUnableToDecodeHolder = ErrorMessage{"invalid_token", "Could not read the holder inside the token."}
43
var ErrorMessageNoSuchSession = ErrorMessage{"no_session", "Session with the requested id is not available."}
44
var ErrorMessageInvalidSdJwt = ErrorMessage{"invalid_sdjwt", "SdJwt does not contain all required fields."}
45

46
func getApiVerifier() verifier.Verifier {
1✔
47
        if apiVerifier == nil {
1✔
48
                apiVerifier = verifier.GetVerifier()
×
49
        }
×
50
        return apiVerifier
1✔
51
}
52

53
func getPresentationParser() verifier.PresentationParser {
1✔
54
        if presentationParser == nil {
1✔
55
                presentationParser = verifier.GetPresentationParser()
×
56
        }
×
57
        return presentationParser
1✔
58
}
59
func getSdJwtParser() verifier.SdJwtParser {
1✔
60
        if sdJwtParser == nil {
1✔
NEW
61
                sdJwtParser = verifier.GetSdJwtParser()
×
NEW
62
        }
×
63
        return sdJwtParser
1✔
64
}
65

66
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
67
func GetToken(c *gin.Context) {
1✔
68

1✔
69
        logging.Log().Debugf("%v", c.Request)
1✔
70
        grantType, grantTypeExists := c.GetPostForm("grant_type")
1✔
71
        if !grantTypeExists {
2✔
72
                logging.Log().Debug("No grant_type present in the request.")
1✔
73
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
1✔
74
                return
1✔
75
        }
1✔
76

77
        if grantType == common.TYPE_CODE {
2✔
78
                handleTokenTypeCode(c)
1✔
79
        } else if grantType == common.TYPE_VP_TOKEN {
3✔
80
                handleTokenTypeVPToken(c, c.GetHeader("client_id"))
1✔
81
        } else {
2✔
82
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedGrantType)
1✔
83
        }
1✔
84
}
85

86
// GetToken - Token endpoint to exchange the authorization code with the actual JWT.
87
func GetTokenForService(c *gin.Context) {
×
88

×
89
        logging.Log().Debugf("%v", c.Request)
×
90
        grantType, grantTypeExists := c.GetPostForm("grant_type")
×
91
        if !grantTypeExists {
×
92
                logging.Log().Debug("No grant_type present in the request.")
×
93
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
×
94
                return
×
95
        }
×
96

97
        if grantType == common.TYPE_CODE {
×
98
                handleTokenTypeCode(c)
×
99
        } else if grantType == common.TYPE_VP_TOKEN {
×
100
                handleTokenTypeVPToken(c, c.Param("service_id"))
×
101
        } else {
×
102
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedGrantType)
×
103
        }
×
104
}
105

106
func handleTokenTypeVPToken(c *gin.Context, clientId string) {
1✔
107
        var requestBody TokenRequestBody
1✔
108

1✔
109
        vpToken, vpTokenExists := c.GetPostForm("vp_token")
1✔
110
        if !vpTokenExists {
2✔
111
                logging.Log().Debug("No vp token present in the request.")
1✔
112
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
113
                return
1✔
114
        }
1✔
115

116
        logging.Log().Warnf("Got token %s", vpToken)
1✔
117

1✔
118
        // not used at the moment
1✔
119
        // presentationSubmission, presentationSubmissionExists := c.GetPostForm("presentation_submission")
1✔
120
        // if !presentationSubmissionExists {
1✔
121
        //        logging.Log().Debug("No presentation submission present in the request.")
1✔
122
        //        c.AbortWithStatusJSON(400, ErrorMessageNoPresentationSubmission)
1✔
123
        //        return
1✔
124
        //}
1✔
125

1✔
126
        scope, scopeExists := c.GetPostForm("scope")
1✔
127
        if !scopeExists {
2✔
128
                logging.Log().Debug("No scope present in the request.")
1✔
129
                c.AbortWithStatusJSON(400, ErrorMessageNoScope)
1✔
130
                return
1✔
131
        }
1✔
132

133
        presentation, err := extractVpFromToken(c, vpToken)
1✔
134
        if err != nil {
1✔
135
                logging.Log().Warnf("Was not able to extract the credentials from the vp_token. E: %v", err)
×
136
                return
×
137
        }
×
138

139
        scopes := strings.Split(scope, ",")
1✔
140

1✔
141
        // Subject is empty since multiple VCs with different subjects can be provided
1✔
142
        expiration, signedToken, err := getApiVerifier().GenerateToken(clientId, "", clientId, scopes, presentation)
1✔
143
        if err != nil {
1✔
144
                logging.Log().Error("Failure during generating M2M token: ", err)
×
145
                c.AbortWithStatusJSON(400, err)
×
146
                return
×
147
        }
×
148
        response := TokenResponse{"Bearer", float32(expiration), signedToken, requestBody.Scope, ""}
1✔
149
        logging.Log().Infof("Generated and signed token: %v", response)
1✔
150
        c.JSON(http.StatusOK, response)
1✔
151
}
152

153
func handleTokenTypeCode(c *gin.Context) {
1✔
154

1✔
155
        code, codeExists := c.GetPostForm("code")
1✔
156
        if !codeExists {
2✔
157
                logging.Log().Debug("No code present in the request.")
1✔
158
                c.AbortWithStatusJSON(400, ErrorMessageNoCode)
1✔
159
                return
1✔
160
        }
1✔
161

162
        redirectUri, redirectUriExists := c.GetPostForm("redirect_uri")
1✔
163
        if !redirectUriExists {
2✔
164
                logging.Log().Debug("No redircet_uri present in the request.")
1✔
165
                c.AbortWithStatusJSON(400, ErrorMessageNoRedircetUri)
1✔
166
                return
1✔
167
        }
1✔
168
        jwt, expiration, err := getApiVerifier().GetToken(code, redirectUri)
1✔
169

1✔
170
        if err != nil {
2✔
171
                c.AbortWithStatusJSON(403, ErrorMessage{Summary: err.Error()})
1✔
172
                return
1✔
173
        }
1✔
174

175
        c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
1✔
176
}
177

178
// StartSIOPSameDevice - Starts the siop flow for credentials hold by the same device
179
func StartSIOPSameDevice(c *gin.Context) {
1✔
180
        state, stateExists := c.GetQuery("state")
1✔
181
        if !stateExists {
2✔
182
                logging.Log().Debugf("No state was provided.")
1✔
183
                c.AbortWithStatusJSON(400, ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."})
1✔
184
                return
1✔
185
        }
1✔
186
        redirectPath, redirectPathExists := c.GetQuery("redirect_path")
1✔
187
        if !redirectPathExists {
2✔
188
                redirectPath = "/"
1✔
189
        }
1✔
190

191
        protocol := "https"
1✔
192
        if c.Request.TLS == nil {
2✔
193
                protocol = "http"
1✔
194
        }
1✔
195

196
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
197
        if !clientIdExists {
2✔
198
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
199
        }
1✔
200

201
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
202
        if !requestModeExists {
2✔
203
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
204
                requestMode = DEFAULT_REQUEST_MODE
1✔
205
        }
1✔
206

207
        redirect, err := getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, redirectPath, clientId, requestMode)
1✔
208
        if err != nil {
2✔
209
                logging.Log().Warnf("Error starting the same-device flow. Err: %v", err)
1✔
210
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to start the same device flow."})
1✔
211
                return
1✔
212
        }
1✔
213
        c.Redirect(302, redirect)
1✔
214
}
215

216
// VerifierAPIAuthenticationResponse - Stores the credential for the given session
217
func VerifierAPIAuthenticationResponse(c *gin.Context) {
1✔
218
        var state string
1✔
219
        stateForm, stateFormExists := c.GetPostForm("state")
1✔
220
        stateQuery, stateQueryExists := c.GetQuery("state")
1✔
221
        if !stateFormExists && !stateQueryExists {
2✔
222
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
1✔
223
                return
1✔
224
        }
1✔
225
        if stateFormExists {
2✔
226
                state = stateForm
1✔
227
        } else {
1✔
228
                // allow the state submitted through a query parameter for backwards-compatibility
×
229
                state = stateQuery
×
230
        }
×
231

232
        vptoken, tokenExists := c.GetPostForm("vp_token")
1✔
233
        if !tokenExists {
2✔
234
                logging.Log().Info("No token was provided.")
1✔
235
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
236
                return
1✔
237
        }
1✔
238

239
        presentation, err := extractVpFromToken(c, vptoken)
1✔
240
        if err != nil {
2✔
241
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
1✔
242
                return
1✔
243
        }
1✔
244
        handleAuthenticationResponse(c, state, presentation)
1✔
245
}
246

247
// GetVerifierAPIAuthenticationResponse - Stores the credential for the given session
248
func GetVerifierAPIAuthenticationResponse(c *gin.Context) {
×
249
        state, stateExists := c.GetQuery("state")
×
250
        if !stateExists {
×
251
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
×
252
                return
×
253
        }
×
254
        vpToken, tokenExists := c.GetQuery("vp_token")
×
255
        if !tokenExists {
×
256
                logging.Log().Info("No token was provided.")
×
257
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
×
258
                return
×
259
        }
×
260
        presentation, err := extractVpFromToken(c, vpToken)
×
261
        if err != nil {
×
262
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
263
                return
×
264
        }
×
265
        handleAuthenticationResponse(c, state, presentation)
×
266
}
267

268
// GetRequestByReference - Get the request object by reference
269
func GetRequestByReference(c *gin.Context) {
×
270
        sessionId := c.Param("id")
×
271

×
272
        jwt, err := verifier.GetVerifier().GetRequestObject(sessionId)
×
273
        if err != nil {
×
274
                logging.Log().Debugf("No request for  %s. Err: %v", sessionId, err)
×
275
                c.AbortWithStatusJSON(404, ErrorMessageNoSuchSession)
×
276
                return
×
277
        }
×
UNCOV
278
        c.String(http.StatusOK, jwt)
×
279
}
280

281
func extractVpFromToken(c *gin.Context, vpToken string) (parsedPresentation *verifiable.Presentation, err error) {
1✔
282

1✔
283
        tokenBytes := decodeVpString(vpToken)
1✔
284

1✔
285
        logging.Log().Debugf("The token %s.", vpToken)
1✔
286

1✔
287
        isSdJWT, parsedPresentation, err := isSdJWT(c, vpToken)
1✔
288
        if isSdJWT && err != nil {
1✔
NEW
289
                return
×
NEW
290
        }
×
291
        if isSdJWT {
2✔
292
                logging.Log().Debugf("Received an sdJwt: %s", logging.PrettyPrintObject(parsedPresentation))
1✔
293
                return
1✔
294
        }
1✔
295

296
        parsedPresentation, err = getPresentationParser().ParsePresentation(tokenBytes)
1✔
297

1✔
298
        if err != nil {
2✔
299
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
1✔
300
                c.AbortWithStatusJSON(400, ErrorMessageUnableToDecodeToken)
1✔
301
                return
1✔
302
        }
1✔
303
        return
1✔
304
}
305

306
// checks if the presented token contains a single sd-jwt credential. Will be repackage to a presentation for further validation
307
func isSdJWT(c *gin.Context, vpToken string) (isSdJwt bool, presentation *verifiable.Presentation, err error) {
1✔
308
        claims, err := getSdJwtParser().Parse(vpToken)
1✔
309
        if err != nil {
2✔
310
                logging.Log().Debugf("Was not a sdjwt. Err: %v", err)
1✔
311
                return false, presentation, err
1✔
312
        }
1✔
313
        issuer, i_ok := claims["iss"]
1✔
314
        vct, vct_ok := claims["vct"]
1✔
315
        if !i_ok || !vct_ok {
1✔
NEW
316
                logging.Log().Infof("Token does not contain issuer(%v) or vct(%v).", i_ok, vct_ok)
×
NEW
317
                c.AbortWithStatusJSON(400, ErrorMessageInvalidSdJwt)
×
NEW
318
                return true, presentation, errors.New(ErrorMessageInvalidSdJwt.Summary)
×
NEW
319
        }
×
320
        customFields := verifiable.CustomFields{}
1✔
321
        for k, v := range claims {
2✔
322
                if k != "iss" && k != "vct" {
2✔
323
                        customFields[k] = v
1✔
324
                }
1✔
325
        }
326
        subject := verifiable.Subject{CustomFields: customFields}
1✔
327
        contents := verifiable.CredentialContents{Issuer: &verifiable.Issuer{ID: issuer.(string)}, Types: []string{vct.(string)}, Subject: []verifiable.Subject{subject}}
1✔
328
        credential, err := verifiable.CreateCredential(contents, verifiable.CustomFields{})
1✔
329
        if err != nil {
1✔
NEW
330
                logging.Log().Infof("Was not able to create credential from sdJwt. E: %v", err)
×
NEW
331
                c.AbortWithStatusJSON(400, ErrorMessageInvalidSdJwt)
×
NEW
332
                return true, presentation, err
×
NEW
333
        }
×
334
        presentation, err = verifiable.NewPresentation()
1✔
335
        if err != nil {
1✔
NEW
336
                logging.Log().Infof("Was not able to create credpresentation from sdJwt. E: %v", err)
×
NEW
337
                c.AbortWithStatusJSON(400, ErrorMessageInvalidSdJwt)
×
NEW
338
                return true, presentation, err
×
NEW
339
        }
×
340
        presentation.AddCredentials(credential)
1✔
341
        return true, presentation, nil
1✔
342
}
343

344
// decodeVpString - In newer versions of OID4VP the token is not encoded as a whole but only its segments separately. This function covers the older and newer versions
345
func decodeVpString(vpToken string) (tokenBytes []byte) {
1✔
346
        tokenBytes, err := base64.RawURLEncoding.DecodeString(vpToken)
1✔
347
        if err != nil {
2✔
348
                return []byte(vpToken)
1✔
349
        }
1✔
350
        return tokenBytes
1✔
351
}
352

353
func handleAuthenticationResponse(c *gin.Context, state string, presentation *verifiable.Presentation) {
1✔
354

1✔
355
        sameDeviceResponse, err := getApiVerifier().AuthenticationResponse(state, presentation)
1✔
356
        if err != nil {
2✔
357
                logging.Log().Warnf("Was not able to fullfil the authentication response. Err: %v", err)
1✔
358
                c.AbortWithStatusJSON(400, ErrorMessage{Summary: err.Error()})
1✔
359
                return
1✔
360
        }
1✔
361
        if sameDeviceResponse != (verifier.SameDeviceResponse{}) {
2✔
362
                c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s", sameDeviceResponse.RedirectTarget, sameDeviceResponse.SessionId, sameDeviceResponse.Code))
1✔
363
                return
1✔
364
        }
1✔
365
        logging.Log().Debugf("Successfully authenticated %s.", state)
1✔
366
        c.JSON(http.StatusOK, gin.H{})
1✔
367
}
368

369
// VerifierAPIJWKS - Provides the public keys for the given verifier, to be used for verifing the JWTs
370
func VerifierAPIJWKS(c *gin.Context) {
×
371
        c.JSON(http.StatusOK, getApiVerifier().GetJWKS())
×
372
}
×
373

374
// VerifierAPIOpenID
375
func VerifierAPIOpenIDConfiguration(c *gin.Context) {
×
376

×
377
        metadata, err := getApiVerifier().GetOpenIDConfiguration(c.Param("service_id"))
×
378
        if err != nil {
×
379
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the OpenID metadata."})
×
380
                return
×
381
        }
×
382
        c.JSON(http.StatusOK, metadata)
×
383
}
384

385
// VerifierAPIStartSIOP - Initiates the siop flow and returns the 'openid://...' connection string
386
func VerifierAPIStartSIOP(c *gin.Context) {
1✔
387
        state, stateExists := c.GetQuery("state")
1✔
388
        if !stateExists {
2✔
389
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
1✔
390
                // early exit
1✔
391
                return
1✔
392
        }
1✔
393

394
        callback, callbackExists := c.GetQuery("client_callback")
1✔
395
        if !callbackExists {
2✔
396
                c.AbortWithStatusJSON(400, ErrorMessageNoCallback)
1✔
397
                // early exit
1✔
398
                return
1✔
399
        }
1✔
400
        protocol := "https"
1✔
401
        if c.Request.TLS == nil {
2✔
402
                protocol = "http"
1✔
403
        }
1✔
404
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
405
        if !clientIdExists {
2✔
406
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
407
        }
1✔
408

409
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
410
        if !requestModeExists {
2✔
411
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
412
                requestMode = DEFAULT_REQUEST_MODE
1✔
413
        }
1✔
414

415
        connectionString, err := getApiVerifier().StartSiopFlow(c.Request.Host, protocol, callback, state, clientId, requestMode)
1✔
416
        if err != nil {
2✔
417
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the connection string."})
1✔
418
                return
1✔
419
        }
1✔
420
        c.String(http.StatusOK, connectionString)
1✔
421
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc