Skip to content

Commit 622bfd1

Browse files
Merge pull request #4714 from linuxfoundation/unicron-port-user-active-signature-api-to-go
Port /v2/user/<user-uuid>/active-signature to golang
2 parents af9859b + fe0b9f3 commit 622bfd1

File tree

13 files changed

+536
-12
lines changed

13 files changed

+536
-12
lines changed

cla-backend-go/swagger/cla.v2.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2445,6 +2445,49 @@ paths:
24452445
tags:
24462446
- signatures
24472447

2448+
/user/{userID}/active-signature:
2449+
parameters:
2450+
- $ref: "#/parameters/x-request-id"
2451+
- name: userID
2452+
description: the user ID
2453+
in: path
2454+
type: string
2455+
required: true
2456+
pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?4[a-fA-F0-9]{3}-?[89ab][a-fA-F0-9]{3}-?[a-fA-F0-9]{12}$' # uuidv4
2457+
get:
2458+
summary: |
2459+
Returns all metadata associated with a user's active signature.
2460+
{
2461+
'user_id': <user-uuid>,
2462+
'project_id': <project-uuid>,
2463+
'repository_id': <repository-id> (as string),
2464+
'pull_request_id': <PR> (PR number as string),
2465+
*'merge_request_id': <MR> (optional MR number as string - this property can be missing in JSON),
2466+
'return_url': <url-where-user-initiated-signature-from> (for example itHub PR path)'
2467+
}
2468+
Returns null if the user does not have an active signature.
2469+
security: [ ]
2470+
operationId: getUserActiveSignature
2471+
responses:
2472+
'200':
2473+
description: 'Success'
2474+
headers:
2475+
x-request-id:
2476+
type: string
2477+
description: The unique request ID value - assigned/set by the API Gateway based on the session
2478+
schema:
2479+
$ref: '#/definitions/user-active-signature'
2480+
'400':
2481+
$ref: '#/responses/invalid-request'
2482+
'401':
2483+
$ref: '#/responses/unauthorized'
2484+
'403':
2485+
$ref: '#/responses/forbidden'
2486+
'404':
2487+
$ref: '#/responses/not-found'
2488+
tags:
2489+
- sign
2490+
24482491
/signatures/project/{projectSFID}/company/{companyID}/employee:
24492492
get:
24502493
summary: Get project company signatures for the employees
@@ -5006,6 +5049,9 @@ definitions:
50065049
user:
50075050
$ref: './common/user.yaml'
50085051

5052+
user-active-signature:
5053+
$ref: './common/user-active-signature.yaml'
5054+
50095055
signatures:
50105056
$ref: './common/signatures.yaml'
50115057

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright The Linux Foundation and each contributor to CommunityBridge.
2+
# SPDX-License-Identifier: MIT
3+
4+
type: object
5+
x-nullable: true
6+
title: User Active Signature
7+
description: >
8+
Returns all metadata associated with a user's active signature.
9+
Returns `null` if the user does not have an active signature.
10+
properties:
11+
user_id:
12+
$ref: './common/properties/internal-id.yaml'
13+
description: The unique internal UUID of the user
14+
project_id:
15+
$ref: './common/properties/internal-id.yaml'
16+
description: The unique UUID of the associated project
17+
repository_id:
18+
type: string
19+
description: The unique ID of the associated repository (number stored as string)
20+
example: '168926425'
21+
pull_request_id:
22+
type: string
23+
description: The pull request ID related to the signature (number stored as string)
24+
example: '456'
25+
merge_request_id:
26+
type: string
27+
description: The merge request ID related to the signature (optional number stored as string, thsi property can be missing in JSON)
28+
example: '456'
29+
x-nullable: true
30+
return_url:
31+
type: string
32+
format: uri
33+
description: The return URL where the user initiated the signature (for example GitHub PR path)
34+
example: https://github.com/veer-missingid2/repo03/pull/3

cla-backend-go/v2/sign/handlers.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"github.com/linuxfoundation/easycla/cla-backend-go/gen/v2/restapi/operations/sign"
2525
"github.com/linuxfoundation/easycla/cla-backend-go/utils"
2626
"github.com/linuxfoundation/easycla/cla-backend-go/v2/organization-service/client/organizations"
27+
28+
"github.com/go-openapi/runtime"
2729
)
2830

2931
var (
@@ -248,6 +250,35 @@ func Configure(api *operations.EasyclaAPI, service Service, userService users.Se
248250
return sign.NewCclaCallbackOK()
249251
})
250252

253+
api.SignGetUserActiveSignatureHandler = sign.GetUserActiveSignatureHandlerFunc(
254+
func(params sign.GetUserActiveSignatureParams) middleware.Responder {
255+
reqId := utils.GetRequestID(params.XREQUESTID)
256+
ctx := context.WithValue(params.HTTPRequest.Context(), utils.XREQUESTIDKey, reqId)
257+
f := logrus.Fields{
258+
"functionName": "v2.sign.handlers.SignGetUserActiveSignatureHandler",
259+
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
260+
"userID": params.UserID,
261+
}
262+
var resp *models.UserActiveSignature
263+
var err error
264+
265+
log.WithFields(f).Debug("getting user active signature")
266+
resp, err = service.GetUserActiveSignature(ctx, params.UserID)
267+
if err != nil {
268+
return sign.NewGetUserActiveSignatureBadRequest().WithPayload(errorResponse(reqId, err))
269+
}
270+
if resp == nil {
271+
return middleware.ResponderFunc(func(w http.ResponseWriter, _ runtime.Producer) {
272+
w.Header().Set("Content-Type", "application/json")
273+
w.WriteHeader(http.StatusOK)
274+
if _, err := w.Write([]byte("null")); err != nil {
275+
log.WithFields(f).WithError(err).Warn("failed to write null response")
276+
}
277+
})
278+
}
279+
return sign.NewGetUserActiveSignatureOK().WithPayload(resp)
280+
})
281+
251282
}
252283

253284
type codedResponse interface {

cla-backend-go/v2/sign/service.go

Lines changed: 139 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ type Service interface {
9090
SignedIndividualCallbackGitlab(ctx context.Context, payload []byte, userID, organizationID, repositoryID, mergeRequestID string) error
9191
SignedIndividualCallbackGerrit(ctx context.Context, payload []byte, userID string) error
9292
SignedCorporateCallback(ctx context.Context, payload []byte, companyID, projectID string) error
93+
GetUserActiveSignature(ctx context.Context, userID string) (*models.UserActiveSignature, error)
9394
}
9495

9596
// service
@@ -1539,7 +1540,7 @@ func (s *service) getIndividualSignatureCallbackURLGitlab(ctx context.Context, u
15391540
if found, ok := metadata["merge_request_id"].(string); ok {
15401541
mergeRequestID = found
15411542
} else {
1542-
log.WithFields(f).WithError(err).Warnf("unable to get pull request ID for user: %s", userID)
1543+
log.WithFields(f).WithError(err).Warnf("unable to get merge request ID for user: %s", userID)
15431544
return "", err
15441545
}
15451546

@@ -1607,11 +1608,28 @@ func (s *service) getIndividualSignatureCallbackURL(ctx context.Context, userID
16071608
log.WithFields(f).Debugf("found pull request ID: %s", pullRequestID)
16081609

16091610
// Get installation ID through a helper function
1610-
log.WithFields(f).Debugf("getting repository...")
1611+
installationId, err = s.getInstallationIDFromRepositoryID(ctx, repositoryID)
1612+
if err != nil {
1613+
log.WithFields(f).WithError(err).Warnf("unable to get github organization for repository ID: %s", repositoryID)
1614+
return "", err
1615+
}
1616+
1617+
callbackURL := fmt.Sprintf("%s/v4/signed/individual/%d/%s/%s", s.ClaV4ApiURL, installationId, repositoryID, pullRequestID)
1618+
return callbackURL, nil
1619+
}
1620+
1621+
func (s *service) getInstallationIDFromRepositoryID(ctx context.Context, repositoryID string) (int64, error) {
1622+
var installationId int64
1623+
f := logrus.Fields{
1624+
"functionName": "sign.getInstallationIDFromRepositoryID",
1625+
"repositoryID": repositoryID,
1626+
}
1627+
// Get installation ID through a helper function
1628+
log.WithFields(f).Debugf("getting repository for ID=%s...", repositoryID)
16111629
githubRepository, err := s.repositoryService.GetRepositoryByExternalID(ctx, repositoryID)
16121630
if err != nil {
16131631
log.WithFields(f).WithError(err).Warnf("unable to get installation ID for repository ID: %s", repositoryID)
1614-
return "", err
1632+
return 0, err
16151633
}
16161634

16171635
// Get github organization
@@ -1620,17 +1638,16 @@ func (s *service) getIndividualSignatureCallbackURL(ctx context.Context, userID
16201638

16211639
if err != nil {
16221640
log.WithFields(f).WithError(err).Warnf("unable to get github organization for repository ID: %s", repositoryID)
1623-
return "", err
1641+
return 0, err
16241642
}
16251643

16261644
installationId = githubOrg.OrganizationInstallationID
16271645
if installationId == 0 {
1628-
log.WithFields(f).WithError(err).Warnf("unable to get installation ID for repository ID: %s", repositoryID)
1629-
return "", err
1646+
log.WithFields(f).Warnf("unable to get installation ID for repository ID: %s", repositoryID)
1647+
return 0, err
16301648
}
16311649

1632-
callbackURL := fmt.Sprintf("%s/v4/signed/individual/%d/%s/%s", s.ClaV4ApiURL, installationId, repositoryID, pullRequestID)
1633-
return callbackURL, nil
1650+
return installationId, nil
16341651
}
16351652

16361653
//nolint:gocyclo
@@ -2769,3 +2786,117 @@ func claSignatoryEmailContent(params ClaSignatoryEmailParams) (string, string) {
27692786

27702787
return emailSubject, emailBody
27712788
}
2789+
2790+
func (s *service) getActiveSignatureReturnURL(ctx context.Context, userID string, metadata map[string]interface{}) (string, error) {
2791+
2792+
f := logrus.Fields{
2793+
"functionName": "sign.getActiveSignatureReturnURL",
2794+
}
2795+
2796+
var returnURL, rId string
2797+
var err, err2 error
2798+
var pullRequestID int
2799+
var repositoryID int64
2800+
var installationID int64
2801+
2802+
if found, ok := metadata["pull_request_id"]; ok && found != nil {
2803+
prId := fmt.Sprintf("%v", found)
2804+
pullRequestID, err2 = strconv.Atoi(prId)
2805+
if err2 != nil {
2806+
log.WithFields(f).WithError(err2).Warnf("unable to get pull request ID for user: %s", userID)
2807+
return "", err2
2808+
}
2809+
} else {
2810+
err2 = errors.New("missing pull_request_id in metadata")
2811+
log.WithFields(f).WithError(err2).Warnf("unable to get pull request ID for user: %s", userID)
2812+
return "", err2
2813+
}
2814+
2815+
if found, ok := metadata["repository_id"]; ok && found != nil {
2816+
rId = fmt.Sprintf("%v", found)
2817+
repositoryID, err2 = strconv.ParseInt(rId, 10, 64)
2818+
if err2 != nil {
2819+
log.WithFields(f).WithError(err2).Warnf("unable to get repository ID for user: %s", userID)
2820+
return "", err2
2821+
}
2822+
} else {
2823+
err2 = errors.New("missing repository_id in metadata")
2824+
log.WithFields(f).WithError(err2).Warnf("unable to get repository ID for user: %s", userID)
2825+
return "", err2
2826+
}
2827+
2828+
// Get installation ID through a helper function
2829+
installationID, err = s.getInstallationIDFromRepositoryID(ctx, rId)
2830+
if err != nil {
2831+
log.WithFields(f).WithError(err).Warnf("unable to get github organization for repository ID: %v", repositoryID)
2832+
return "", err
2833+
}
2834+
2835+
returnURL, err = github.GetReturnURL(ctx, installationID, repositoryID, pullRequestID)
2836+
2837+
if err != nil {
2838+
return "", err
2839+
}
2840+
2841+
return returnURL, nil
2842+
}
2843+
2844+
func (s *service) GetUserActiveSignature(ctx context.Context, userID string) (*models.UserActiveSignature, error) {
2845+
f := logrus.Fields{
2846+
"functionName": "sign.GetUserActiveSignature",
2847+
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
2848+
"userID": userID,
2849+
}
2850+
activeSignatureMetadata, err := s.storeRepository.GetActiveSignatureMetaData(ctx, userID)
2851+
if err != nil {
2852+
log.WithFields(f).WithError(err).Warnf("unable to get active signature meta data for user: %s", userID)
2853+
return nil, err
2854+
}
2855+
log.WithFields(f).Debugf("active signature metadata: %+v", activeSignatureMetadata)
2856+
if len(activeSignatureMetadata) == 0 {
2857+
return nil, nil
2858+
}
2859+
var (
2860+
mergeRequestId *string
2861+
isGitlab bool
2862+
returnURL string
2863+
pullRequestId string
2864+
repositoryId string
2865+
)
2866+
if mrId, ok := activeSignatureMetadata["merge_request_id"]; ok && mrId != nil {
2867+
mrStr := fmt.Sprintf("%v", mrId)
2868+
mergeRequestId = &mrStr
2869+
isGitlab = true
2870+
}
2871+
if isGitlab {
2872+
var ok bool
2873+
returnURL, ok = activeSignatureMetadata["return_url"].(string)
2874+
if !ok {
2875+
log.WithFields(f).Warnf("missing return_url in metadata while merge_request_id is present: %+v", activeSignatureMetadata)
2876+
}
2877+
} else {
2878+
returnURL, err = s.getActiveSignatureReturnURL(ctx, userID, activeSignatureMetadata)
2879+
if err != nil {
2880+
log.WithFields(f).WithError(err).Warnf("unable to get active signature return url for user: %s", userID)
2881+
return nil, err
2882+
}
2883+
}
2884+
projectId, ok := activeSignatureMetadata["project_id"].(string)
2885+
if !ok {
2886+
log.WithFields(f).Warnf("missing project_id in metadata: %+v", activeSignatureMetadata)
2887+
}
2888+
if val, ok := activeSignatureMetadata["pull_request_id"]; ok && val != nil {
2889+
pullRequestId = fmt.Sprintf("%v", val)
2890+
}
2891+
if val, ok := activeSignatureMetadata["repository_id"]; ok && val != nil {
2892+
repositoryId = fmt.Sprintf("%v", val)
2893+
}
2894+
return &models.UserActiveSignature{
2895+
MergeRequestID: mergeRequestId,
2896+
ProjectID: projectId,
2897+
PullRequestID: pullRequestId,
2898+
RepositoryID: repositoryId,
2899+
ReturnURL: strfmt.URI(returnURL),
2900+
UserID: userID,
2901+
}, nil
2902+
}

cla-backend/cla/routes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ def request_company_ccla(
232232
# return cla.controllers.user.request_company_admin_access(str(user_id), str(company_id))
233233

234234

235+
# LG: This is ported to golang and no longer used in dev (still used in prod)
235236
@hug.get("/user/{user_id}/active-signature", versions=2)
236237
def get_user_active_signature(user_id: hug.types.uuid):
237238
"""
@@ -773,7 +774,7 @@ def get_projects(auth_user: check_auth):
773774
del project["project_external_id"]
774775
return projects
775776

776-
777+
# LG: This is ported to golang and no longer used in dev (still used in prod).
777778
@hug.get("/project/{project_id}", versions=2)
778779
def get_project(project_id: hug.types.uuid):
779780
"""

tests/py2go/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@
2121
- Manually via `cURL`: `` curl -s -XGET http://127.0.0.1:5001/v4/project-compat/01af041c-fa69-4052-a23c-fb8c1d3bef24 | jq . ``.
2222
- To manually see given project values if APIs differ (to dewbug): `` aws --region us-east-1 --profile lfproduct-dev dynamodb get-item --table-name cla-dev-projects --key '{"project_id": {"S": "4a855799-0aea-4e01-98b7-ef3da09df478"}}' | jq '.Item' ``.
2323
- And `` aws --region us-east-1 --profile lfproduct-dev dynamodb query --table-name cla-dev-projects-cla-groups --index-name cla-group-id-index --key-condition-expression "cla_group_id = :project_id" --expression-attribute-values '{":project_id":{"S":"4a855799-0aea-4e01-98b7-ef3da09df478"}}' | jq '.Items' ``.
24+
- `` DEBUG='' USER_UUID=b817eb57-045a-4fe0-8473-fbb416a01d70 PY_API_URL=https://api.lfcla.dev.platform.linuxfoundation.org go test -v -run '^TestUserActiveSignatureAPI$' ``.
25+
- `` REPO_ID=466156917 PR_ID=3 DEBUG=1 go test -v -run '^TestUserActiveSignatureAPI$' ``.
26+
- `` MAX_PARALLEL=2 DEBUG='' go test -v -run '^TestAllUserActiveSignatureAPI$' ``.

0 commit comments

Comments
 (0)