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

FIWARE / VCVerifier / 15068141810

16 May 2025 12:11PM UTC coverage: 45.358% (-1.0%) from 46.375%
15068141810

Pull #57

github

web-flow
Update README.md

Co-authored-by: Tim Smyth <33017641+pulledtim@users.noreply.github.com>
Pull Request #57: Support request byValue and byReference and presentationDefinition

150 of 289 new or added lines in 7 files covered. (51.9%)

40 existing lines in 5 files now uncovered.

1202 of 2650 relevant lines covered (45.36%)

0.51 hits per line

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

73.86
/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
        "fmt"
15
        "net/http"
16
        "strings"
17

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

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

26
var apiVerifier verifier.Verifier
27
var presentationParser verifier.PresentationParser
28

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

43
func getApiVerifier() verifier.Verifier {
1✔
44
        if apiVerifier == nil {
1✔
45
                apiVerifier = verifier.GetVerifier()
×
46
        }
×
47
        return apiVerifier
1✔
48
}
49

50
func getPresentationParser() verifier.PresentationParser {
1✔
51
        if presentationParser == nil {
1✔
52
                presentationParser = verifier.GetPresentationParser()
×
53
        }
×
54
        return presentationParser
1✔
55
}
56

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

1✔
60
        logging.Log().Debugf("%v", c.Request)
1✔
61
        grantType, grantTypeExists := c.GetPostForm("grant_type")
1✔
62
        if !grantTypeExists {
2✔
63
                logging.Log().Debug("No grant_type present in the request.")
1✔
64
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
1✔
65
                return
1✔
66
        }
1✔
67

68
        if grantType == common.TYPE_CODE {
2✔
69
                handleTokenTypeCode(c)
1✔
70
        } else if grantType == common.TYPE_VP_TOKEN {
3✔
71
                handleTokenTypeVPToken(c, c.GetHeader("client_id"))
1✔
72
        } else {
2✔
73
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedGrantType)
1✔
74
        }
1✔
75
}
76

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

×
80
        logging.Log().Debugf("%v", c.Request)
×
81
        grantType, grantTypeExists := c.GetPostForm("grant_type")
×
82
        if !grantTypeExists {
×
83
                logging.Log().Debug("No grant_type present in the request.")
×
84
                c.AbortWithStatusJSON(400, ErrorMessagNoGrantType)
×
85
                return
×
86
        }
×
87

88
        if grantType == common.TYPE_CODE {
×
89
                handleTokenTypeCode(c)
×
90
        } else if grantType == common.TYPE_VP_TOKEN {
×
91
                handleTokenTypeVPToken(c, c.Param("service_id"))
×
92
        } else {
×
93
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedGrantType)
×
94
        }
×
95
}
96

97
func handleTokenTypeVPToken(c *gin.Context, clientId string) {
1✔
98
        var requestBody TokenRequestBody
1✔
99

1✔
100
        vpToken, vpTokenExists := c.GetPostForm("vp_token")
1✔
101
        if !vpTokenExists {
2✔
102
                logging.Log().Debug("No vp token present in the request.")
1✔
103
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
104
                return
1✔
105
        }
1✔
106

107
        logging.Log().Warnf("Got token %s", vpToken)
1✔
108

1✔
109
        // not used at the moment
1✔
110
        // presentationSubmission, presentationSubmissionExists := c.GetPostForm("presentation_submission")
1✔
111
        // if !presentationSubmissionExists {
1✔
112
        //        logging.Log().Debug("No presentation submission present in the request.")
1✔
113
        //        c.AbortWithStatusJSON(400, ErrorMessageNoPresentationSubmission)
1✔
114
        //        return
1✔
115
        //}
1✔
116

1✔
117
        scope, scopeExists := c.GetPostForm("scope")
1✔
118
        if !scopeExists {
2✔
119
                logging.Log().Debug("No scope present in the request.")
1✔
120
                c.AbortWithStatusJSON(400, ErrorMessageNoScope)
1✔
121
                return
1✔
122
        }
1✔
123

124
        presentation, err := extractVpFromToken(c, vpToken)
1✔
125
        if err != nil {
1✔
126
                logging.Log().Warnf("Was not able to extract the credentials from the vp_token. E: %v", err)
×
127
                return
×
128
        }
×
129

130
        scopes := strings.Split(scope, ",")
1✔
131

1✔
132
        // Subject is empty since multiple VCs with different subjects can be provided
1✔
133
        expiration, signedToken, err := getApiVerifier().GenerateToken(clientId, "", clientId, scopes, presentation)
1✔
134
        if err != nil {
1✔
135
                logging.Log().Error("Failure during generating M2M token: ", err)
×
136
                c.AbortWithStatusJSON(400, err)
×
137
                return
×
138
        }
×
139
        response := TokenResponse{"Bearer", float32(expiration), signedToken, requestBody.Scope, ""}
1✔
140
        logging.Log().Infof("Generated and signed token: %v", response)
1✔
141
        c.JSON(http.StatusOK, response)
1✔
142
}
143

144
func handleTokenTypeCode(c *gin.Context) {
1✔
145

1✔
146
        code, codeExists := c.GetPostForm("code")
1✔
147
        if !codeExists {
2✔
148
                logging.Log().Debug("No code present in the request.")
1✔
149
                c.AbortWithStatusJSON(400, ErrorMessageNoCode)
1✔
150
                return
1✔
151
        }
1✔
152

153
        redirectUri, redirectUriExists := c.GetPostForm("redirect_uri")
1✔
154
        if !redirectUriExists {
2✔
155
                logging.Log().Debug("No redircet_uri present in the request.")
1✔
156
                c.AbortWithStatusJSON(400, ErrorMessageNoRedircetUri)
1✔
157
                return
1✔
158
        }
1✔
159
        jwt, expiration, err := getApiVerifier().GetToken(code, redirectUri)
1✔
160

1✔
161
        if err != nil {
2✔
162
                c.AbortWithStatusJSON(403, ErrorMessage{Summary: err.Error()})
1✔
163
                return
1✔
164
        }
1✔
165

166
        c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
1✔
167
}
168

169
// StartSIOPSameDevice - Starts the siop flow for credentials hold by the same device
170
func StartSIOPSameDevice(c *gin.Context) {
1✔
171
        state, stateExists := c.GetQuery("state")
1✔
172
        if !stateExists {
2✔
173
                logging.Log().Debugf("No state was provided.")
1✔
174
                c.AbortWithStatusJSON(400, ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."})
1✔
175
                return
1✔
176
        }
1✔
177
        redirectPath, redirectPathExists := c.GetQuery("redirect_path")
1✔
178
        if !redirectPathExists {
2✔
179
                redirectPath = "/"
1✔
180
        }
1✔
181

182
        protocol := "https"
1✔
183
        if c.Request.TLS == nil {
2✔
184
                protocol = "http"
1✔
185
        }
1✔
186

187
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
188
        if !clientIdExists {
2✔
189
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
190
        }
1✔
191

192
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
193
        if !requestModeExists {
2✔
194
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
195
                requestMode = DEFAULT_REQUEST_MODE
1✔
196
        }
1✔
197

198
        redirect, err := getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, redirectPath, clientId, requestMode)
1✔
199
        if err != nil {
2✔
200
                logging.Log().Warnf("Error starting the same-device flow. Err: %v", err)
1✔
201
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to start the same device flow."})
1✔
202
                return
1✔
203
        }
1✔
204
        c.Redirect(302, redirect)
1✔
205
}
206

207
// VerifierAPIAuthenticationResponse - Stores the credential for the given session
208
func VerifierAPIAuthenticationResponse(c *gin.Context) {
1✔
209
        var state string
1✔
210
        stateForm, stateFormExists := c.GetPostForm("state")
1✔
211
        stateQuery, stateQueryExists := c.GetQuery("state")
1✔
212
        if !stateFormExists && !stateQueryExists {
2✔
213
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
1✔
214
                return
1✔
215
        }
1✔
216
        if stateFormExists {
2✔
217
                state = stateForm
1✔
218
        } else {
1✔
219
                // allow the state submitted through a query parameter for backwards-compatibility
×
220
                state = stateQuery
×
221
        }
×
222

223
        vptoken, tokenExists := c.GetPostForm("vp_token")
1✔
224
        if !tokenExists {
2✔
225
                logging.Log().Info("No token was provided.")
1✔
226
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
227
                return
1✔
228
        }
1✔
229

230
        presentation, err := extractVpFromToken(c, vptoken)
1✔
231
        if err != nil {
2✔
232
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
1✔
233
                return
1✔
234
        }
1✔
235
        handleAuthenticationResponse(c, state, presentation)
1✔
236
}
237

238
// GetVerifierAPIAuthenticationResponse - Stores the credential for the given session
239
func GetVerifierAPIAuthenticationResponse(c *gin.Context) {
×
240
        state, stateExists := c.GetQuery("state")
×
241
        if !stateExists {
×
242
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
×
243
                return
×
244
        }
×
245
        vpToken, tokenExists := c.GetQuery("vp_token")
×
246
        if !tokenExists {
×
247
                logging.Log().Info("No token was provided.")
×
248
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
×
249
                return
×
250
        }
×
251
        presentation, err := extractVpFromToken(c, vpToken)
×
252
        if err != nil {
×
253
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
254
                return
×
255
        }
×
256
        handleAuthenticationResponse(c, state, presentation)
×
257
}
258

259
// GetRequestByReference - Get the request object by reference
NEW
260
func GetRequestByReference(c *gin.Context) {
×
NEW
261
        sessionId := c.Param("id")
×
NEW
262

×
NEW
263
        jwt, err := verifier.GetVerifier().GetRequestObject(sessionId)
×
NEW
264
        if err != nil {
×
NEW
265
                logging.Log().Debugf("No request for  %s. Err: %v", sessionId, err)
×
NEW
266
                c.AbortWithStatusJSON(404, ErrorMessageNoSuchSession)
×
NEW
267
                return
×
NEW
268
        }
×
269

NEW
270
        c.String(http.StatusOK, jwt)
×
271
}
272

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

1✔
275
        tokenBytes := decodeVpString(vpToken)
1✔
276

1✔
277
        parsedPresentation, err = getPresentationParser().ParsePresentation(tokenBytes)
1✔
278
        if err != nil {
2✔
279
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
1✔
280
                c.AbortWithStatusJSON(400, ErrorMessageUnableToDecodeToken)
1✔
281
                return
1✔
282
        }
1✔
283
        return
1✔
284
}
285

286
// 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
287
func decodeVpString(vpToken string) (tokenBytes []byte) {
1✔
288
        tokenBytes, err := base64.RawURLEncoding.DecodeString(vpToken)
1✔
289
        if err != nil {
2✔
290
                return []byte(vpToken)
1✔
291
        }
1✔
292
        return tokenBytes
1✔
293
}
294

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

1✔
297
        sameDeviceResponse, err := getApiVerifier().AuthenticationResponse(state, presentation)
1✔
298
        if err != nil {
2✔
299
                logging.Log().Warnf("Was not able to fullfil the authentication response. Err: %v", err)
1✔
300
                c.AbortWithStatusJSON(400, ErrorMessage{Summary: err.Error()})
1✔
301
                return
1✔
302
        }
1✔
303
        if sameDeviceResponse != (verifier.SameDeviceResponse{}) {
2✔
304
                c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s", sameDeviceResponse.RedirectTarget, sameDeviceResponse.SessionId, sameDeviceResponse.Code))
1✔
305
                return
1✔
306
        }
1✔
307
        logging.Log().Debugf("Successfully authenticated %s.", state)
1✔
308
        c.JSON(http.StatusOK, gin.H{})
1✔
309
}
310

311
// VerifierAPIJWKS - Provides the public keys for the given verifier, to be used for verifing the JWTs
312
func VerifierAPIJWKS(c *gin.Context) {
×
313
        c.JSON(http.StatusOK, getApiVerifier().GetJWKS())
×
314
}
×
315

316
// VerifierAPIOpenID
317
func VerifierAPIOpenIDConfiguration(c *gin.Context) {
×
318

×
319
        metadata, err := getApiVerifier().GetOpenIDConfiguration(c.Param("service_id"))
×
320
        if err != nil {
×
321
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the OpenID metadata."})
×
322
                return
×
323
        }
×
324
        c.JSON(http.StatusOK, metadata)
×
325
}
326

327
// VerifierAPIStartSIOP - Initiates the siop flow and returns the 'openid://...' connection string
328
func VerifierAPIStartSIOP(c *gin.Context) {
1✔
329
        state, stateExists := c.GetQuery("state")
1✔
330
        if !stateExists {
2✔
331
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
1✔
332
                // early exit
1✔
333
                return
1✔
334
        }
1✔
335

336
        callback, callbackExists := c.GetQuery("client_callback")
1✔
337
        if !callbackExists {
2✔
338
                c.AbortWithStatusJSON(400, ErrorMessageNoCallback)
1✔
339
                // early exit
1✔
340
                return
1✔
341
        }
1✔
342
        protocol := "https"
1✔
343
        if c.Request.TLS == nil {
2✔
344
                protocol = "http"
1✔
345
        }
1✔
346
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
347
        if !clientIdExists {
2✔
348
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
349
        }
1✔
350

351
        requestMode, requestModeExists := c.GetQuery("request_mode")
1✔
352
        if !requestModeExists {
2✔
353
                logging.Log().Infof("Using default request mode %s.", DEFAULT_REQUEST_MODE)
1✔
354
                requestMode = DEFAULT_REQUEST_MODE
1✔
355
        }
1✔
356

357
        connectionString, err := getApiVerifier().StartSiopFlow(c.Request.Host, protocol, callback, state, clientId, requestMode)
1✔
358
        if err != nil {
2✔
359
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the connection string."})
1✔
360
                return
1✔
361
        }
1✔
362
        c.String(http.StatusOK, connectionString)
1✔
363
}
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