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

FIWARE / VCVerifier / 8357898663

20 Mar 2024 11:08AM UTC coverage: 50.0% (-7.6%) from 57.617%
8357898663

Pull #28

github

web-flow
Merge branch 'main' into update-new-walt
Pull Request #28: remove walt, support jwt

114 of 215 new or added lines in 8 files covered. (53.02%)

85 existing lines in 4 files now uncovered.

934 of 1868 relevant lines covered (50.0%)

0.56 hits per line

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

70.98
/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/piprate/json-gold/ld"
22
        "github.com/trustbloc/vc-go/proof/defaults"
23
        "github.com/trustbloc/vc-go/verifiable"
24

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

28
var apiVerifier verifier.Verifier
29
var presentationOptions = []verifiable.PresentationOpt{
30
        verifiable.WithPresProofChecker(defaults.NewDefaultProofChecker(verifier.JWTVerfificationMethodResolver{})),
31
        verifiable.WithPresJSONLDDocumentLoader(ld.NewDefaultDocumentLoader(http.DefaultClient))}
32

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

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

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

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

64
        if grantType == common.TYPE_CODE {
2✔
65
                handleTokenTypeCode(c)
1✔
66
        } else if grantType == common.TYPE_VP_TOKEN {
3✔
67
                handleTokenTypeVPToken(c)
1✔
68
        } else {
2✔
69
                c.AbortWithStatusJSON(400, ErrorMessageUnsupportedGrantType)
1✔
70
        }
1✔
71
}
72

73
func handleTokenTypeVPToken(c *gin.Context) {
1✔
74
        var requestBody TokenRequestBody
1✔
75

1✔
76
        vpToken, vpTokenExists := c.GetPostForm("vp_token")
1✔
77
        if !vpTokenExists {
2✔
78
                logging.Log().Debug("No vp token present in the request.")
1✔
79
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
1✔
80
                return
1✔
81
        }
1✔
82

83
        logging.Log().Warnf("Got token %s", vpToken)
1✔
84

1✔
85
        // not used at the moment
1✔
86
        // presentationSubmission, presentationSubmissionExists := c.GetPostForm("presentation_submission")
1✔
87
        // if !presentationSubmissionExists {
1✔
88
        //        logging.Log().Debug("No presentation submission present in the request.")
1✔
89
        //        c.AbortWithStatusJSON(400, ErrorMessageNoPresentationSubmission)
1✔
90
        //        return
1✔
91
        //}
1✔
92

1✔
93
        scope, scopeExists := c.GetPostForm("scope")
1✔
94
        if !scopeExists {
2✔
95
                logging.Log().Debug("No scope present in the request.")
1✔
96
                c.AbortWithStatusJSON(400, ErrorMessageNoScope)
1✔
97
                return
1✔
98
        }
1✔
99

100
        presentation, err := extractVpFromToken(c, vpToken)
1✔
101
        if err != nil {
1✔
102
                logging.Log().Warnf("Was not able to extract the credentials from the vp_token.")
×
103
                return
×
104
        }
×
105
        clientId := c.GetHeader("client_id")
1✔
106

1✔
107
        scopes := strings.Split(scope, ",")
1✔
108

1✔
109
        // Subject is empty since multiple VCs with different subjects can be provided
1✔
110
        expiration, signedToken, err := getApiVerifier().GenerateToken(clientId, "", clientId, scopes, presentation)
1✔
111
        if err != nil {
1✔
112
                logging.Log().Error("Failure during generating M2M token: ", err)
×
113
                c.AbortWithStatusJSON(400, err)
×
114
                return
×
115
        }
×
116
        response := TokenResponse{"Bearer", float32(expiration), signedToken, requestBody.Scope, ""}
1✔
117
        logging.Log().Infof("Generated and signed token: %v", response)
1✔
118
        c.JSON(http.StatusOK, response)
1✔
119
}
120

121
func handleTokenTypeCode(c *gin.Context) {
1✔
122

1✔
123
        code, codeExists := c.GetPostForm("code")
1✔
124
        if !codeExists {
2✔
125
                logging.Log().Debug("No code present in the request.")
1✔
126
                c.AbortWithStatusJSON(400, ErrorMessageNoCode)
1✔
127
                return
1✔
128
        }
1✔
129

130
        redirectUri, redirectUriExists := c.GetPostForm("redirect_uri")
1✔
131
        if !redirectUriExists {
2✔
132
                logging.Log().Debug("No redircet_uri present in the request.")
1✔
133
                c.AbortWithStatusJSON(400, ErrorMessageNoRedircetUri)
1✔
134
                return
1✔
135
        }
1✔
136
        jwt, expiration, err := getApiVerifier().GetToken(code, redirectUri)
1✔
137

1✔
138
        if err != nil {
2✔
139
                c.AbortWithStatusJSON(403, ErrorMessage{Summary: err.Error()})
1✔
140
                return
1✔
141
        }
1✔
142

143
        c.JSON(http.StatusOK, TokenResponse{TokenType: "Bearer", ExpiresIn: float32(expiration), AccessToken: jwt})
1✔
144
}
145

146
// StartSIOPSameDevice - Starts the siop flow for credentials hold by the same device
147
func StartSIOPSameDevice(c *gin.Context) {
1✔
148
        state, stateExists := c.GetQuery("state")
1✔
149
        if !stateExists {
2✔
150
                logging.Log().Debugf("No state was provided.")
1✔
151
                c.AbortWithStatusJSON(400, ErrorMessage{"no_state_provided", "Authentication requires a state provided as query parameter."})
1✔
152
                return
1✔
153
        }
1✔
154
        redirectPath, redirectPathExists := c.GetQuery("redirect_path")
1✔
155
        if !redirectPathExists {
2✔
156
                redirectPath = "/"
1✔
157
        }
1✔
158

159
        protocol := "https"
1✔
160
        if c.Request.TLS == nil {
2✔
161
                protocol = "http"
1✔
162
        }
1✔
163

164
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
165
        if !clientIdExists {
2✔
166
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
167
        }
1✔
168

169
        redirect, err := getApiVerifier().StartSameDeviceFlow(c.Request.Host, protocol, state, redirectPath, clientId)
1✔
170
        if err != nil {
2✔
171
                logging.Log().Warnf("Error starting the same-device flow. Err: %v", err)
1✔
172
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to start the same device flow."})
1✔
173
                return
1✔
174
        }
1✔
175
        c.Redirect(302, redirect)
1✔
176
}
177

178
// VerifierAPIAuthenticationResponse - Stores the credential for the given session
179
func VerifierAPIAuthenticationResponse(c *gin.Context) {
1✔
180
        var state string
1✔
181
        stateForm, stateFormExists := c.GetPostForm("state")
1✔
182
        stateQuery, stateQueryExists := c.GetQuery("state")
1✔
183
        if !stateFormExists && !stateQueryExists {
1✔
UNCOV
184
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
×
UNCOV
185
                return
×
UNCOV
186
        }
×
187
        if stateFormExists {
2✔
188
                state = stateForm
1✔
189
        } else {
1✔
NEW
190
                // allow the state submitted through a query parameter for backwards-compatibility
×
NEW
191
                state = stateQuery
×
NEW
192
        }
×
193

194
        vptoken, tokenExists := c.GetPostForm("vp_token")
1✔
195
        if !tokenExists {
1✔
UNCOV
196
                logging.Log().Info("No token was provided.")
×
UNCOV
197
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
×
UNCOV
198
                return
×
UNCOV
199
        }
×
200

201
        presentation, err := extractVpFromToken(c, vptoken)
1✔
202
        if err != nil {
1✔
NEW
203
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
UNCOV
204
                return
×
UNCOV
205
        }
×
206
        handleAuthenticationResponse(c, state, presentation)
1✔
207
}
208

209
// GetVerifierAPIAuthenticationResponse - Stores the credential for the given session
210
func GetVerifierAPIAuthenticationResponse(c *gin.Context) {
×
211
        state, stateExists := c.GetQuery("state")
×
212
        if !stateExists {
×
213
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
×
214
                return
×
215
        }
×
216
        vpToken, tokenExists := c.GetQuery("vp_token")
×
217
        if !tokenExists {
×
218
                logging.Log().Info("No token was provided.")
×
219
                c.AbortWithStatusJSON(400, ErrorMessageNoToken)
×
220
                return
×
221
        }
×
NEW
222
        presentation, err := extractVpFromToken(c, vpToken)
×
223
        if err != nil {
×
NEW
224
                logging.Log().Warnf("Was not able to extract the presentation from the vp_token.")
×
225
                return
×
226
        }
×
NEW
227
        handleAuthenticationResponse(c, state, presentation)
×
228
}
229

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

1✔
232
        tokenBytes, err := base64.RawURLEncoding.DecodeString(vpToken)
1✔
233
        if err != nil {
1✔
234
                logging.Log().Infof("Was not able to decode the form string %s. Err: %v", vpToken, err)
×
235
                c.AbortWithStatusJSON(400, ErrorMessageUnableToDecodeToken)
×
236
                return
×
237
        }
×
238

239
        parsedPresentation, err = verifiable.ParsePresentation(tokenBytes,
1✔
240
                presentationOptions...)
1✔
241
        if err != nil {
1✔
NEW
242
                logging.Log().Infof("Was not able to parse the token %s. Err: %v", vpToken, err)
×
NEW
243
                c.AbortWithStatusJSON(400, ErrorMessageUnableToDecodeToken)
×
UNCOV
244
                return
×
UNCOV
245
        }
×
246
        return
1✔
247
}
248

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

1✔
251
        sameDeviceResponse, err := getApiVerifier().AuthenticationResponse(state, presentation)
1✔
252
        if err != nil {
1✔
UNCOV
253
                logging.Log().Warnf("Was not able to fullfil the authentication response. Err: %v", err)
×
UNCOV
254
                c.AbortWithStatusJSON(400, ErrorMessage{Summary: err.Error()})
×
UNCOV
255
                return
×
UNCOV
256
        }
×
257
        if sameDeviceResponse != (verifier.SameDeviceResponse{}) {
2✔
258
                c.Redirect(302, fmt.Sprintf("%s?state=%s&code=%s", sameDeviceResponse.RedirectTarget, sameDeviceResponse.SessionId, sameDeviceResponse.Code))
1✔
259
                return
1✔
260
        }
1✔
UNCOV
261
        logging.Log().Debugf("Successfully authenticated %s.", state)
×
UNCOV
262
        c.JSON(http.StatusOK, gin.H{})
×
263
}
264

265
// VerifierAPIJWKS - Provides the public keys for the given verifier, to be used for verifing the JWTs
266
func VerifierAPIJWKS(c *gin.Context) {
×
267
        c.JSON(http.StatusOK, getApiVerifier().GetJWKS())
×
268
}
×
269

270
// VerifierAPIOpenID
271
func VerifierAPIOpenIDConfiguration(c *gin.Context) {
×
272

×
273
        metadata, err := getApiVerifier().GetOpenIDConfiguration(c.Param("serviceIdentifier"))
×
274
        if err != nil {
×
275
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the OpenID metadata."})
×
276
                return
×
277
        }
×
278
        c.JSON(http.StatusOK, metadata)
×
279
}
280

281
// VerifierAPIStartSIOP - Initiates the siop flow and returns the 'openid://...' connection string
282
func VerifierAPIStartSIOP(c *gin.Context) {
1✔
283
        state, stateExists := c.GetQuery("state")
1✔
284
        if !stateExists {
2✔
285
                c.AbortWithStatusJSON(400, ErrorMessageNoState)
1✔
286
                // early exit
1✔
287
                return
1✔
288
        }
1✔
289

290
        callback, callbackExists := c.GetQuery("client_callback")
1✔
291
        if !callbackExists {
2✔
292
                c.AbortWithStatusJSON(400, ErrorMessageNoCallback)
1✔
293
                // early exit
1✔
294
                return
1✔
295
        }
1✔
296
        protocol := "https"
1✔
297
        if c.Request.TLS == nil {
2✔
298
                protocol = "http"
1✔
299
        }
1✔
300
        clientId, clientIdExists := c.GetQuery("client_id")
1✔
301
        if !clientIdExists {
2✔
302
                logging.Log().Infof("Start a login flow for a not specified client.")
1✔
303
        }
1✔
304

305
        connectionString, err := getApiVerifier().StartSiopFlow(c.Request.Host, protocol, callback, state, clientId)
1✔
306
        if err != nil {
2✔
307
                c.AbortWithStatusJSON(500, ErrorMessage{err.Error(), "Was not able to generate the connection string."})
1✔
308
                return
1✔
309
        }
1✔
310
        c.String(http.StatusOK, connectionString)
1✔
311
}
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