Skip to content

Commit b9e55d7

Browse files
Merge pull request #4978 from linuxfoundation/unicron-4973-use-primary-email-in-gh-gl-to-docusign
Unicron 4973 use primary email in gh gl to docusign
2 parents 1c5af93 + 4eeea7a commit b9e55d7

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ 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+
user_service "github.com/linuxfoundation/easycla/cla-backend-go/v2/user-service"
2728

2829
"github.com/go-openapi/runtime"
2930
)
@@ -77,6 +78,8 @@ func CCLADocusignMiddleware(next http.Handler) http.Handler {
7778
}
7879

7980
// Configure API call
81+
//
82+
//nolint:gocyclo
8083
func Configure(api *operations.EasyclaAPI, service Service, userService users.Service) {
8184
// Retrieve a list of available templates
8285
api.SignClearCachesHandler = sign.ClearCachesHandlerFunc(
@@ -180,12 +183,36 @@ func Configure(api *operations.EasyclaAPI, service Service, userService users.Se
180183
if userErr != nil {
181184
return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, userErr))
182185
}
183-
if len(user.Emails) == 0 {
186+
if user == nil {
187+
msg := fmt.Sprintf("user not found: %s", *params.Input.UserID)
188+
log.WithFields(f).Warn(msg)
189+
return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, errors.New(msg)))
190+
}
191+
userServiceClient := user_service.GetClient()
192+
if userServiceClient != nil && userServiceClient.IsConfigured() && user.LfUsername != "" {
193+
platformUser, platformUserErr := userServiceClient.GetUserByUsername(user.LfUsername)
194+
if platformUserErr != nil {
195+
log.WithFields(f).WithError(platformUserErr).Warn("unable to fetch platform user for primary email lookup")
196+
} else if platformUser != nil {
197+
for _, email := range platformUser.Emails {
198+
if email != nil && email.IsPrimary != nil && *email.IsPrimary && email.EmailAddress != nil && *email.EmailAddress != "" {
199+
preferredEmail = *email.EmailAddress
200+
break
201+
}
202+
}
203+
}
204+
}
205+
206+
if preferredEmail == "" && len(user.Emails) > 0 {
207+
preferredEmail = user.Emails[0]
208+
}
209+
210+
if preferredEmail == "" {
184211
msg := "no emails found"
185212
log.WithFields(f).Warn(msg)
186213
return sign.NewRequestIndividualSignatureBadRequest().WithPayload(errorResponse(reqId, errors.New(msg)))
187214
}
188-
preferredEmail = user.Emails[0]
215+
189216
log.WithFields(f).Debug("requesting individual signature for github/gitlab")
190217
resp, err = service.RequestIndividualSignature(ctx, params.Input, preferredEmail)
191218
} else if strings.ToLower(params.Input.ReturnURLType) == "gerrit" {

cla-backend-go/v2/user-service/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ func GetClient() *Client {
6161
return userServiceClient
6262
}
6363

64+
// IsConfigured reports whether the user-service client has a usable API gateway URL.
65+
func (usc *Client) IsConfigured() bool {
66+
return usc != nil && strings.TrimSpace(usc.apiGwURL) != ""
67+
}
68+
6469
// GetUsersByUsernames search users by lf username
6570
func (usc *Client) GetUsersByUsernames(lfUsernames []string) ([]*models.User, error) {
6671
f := logrus.Fields{

cla-backend/cla/controllers/signing.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,42 @@ def request_individual_signature(project_id, user_id, return_url_type, return_ur
4242
github = get_repository_service("github")
4343
primary_user_email = github.get_primary_user_email(request)
4444
elif return_url_type.lower() == "gitlab":
45+
primary_user_email = None
46+
4547
try:
4648
cla.log.debug(f"Fetching user details for: {user_id}")
4749
user = User()
4850
user.load(user_id)
4951
except DoesNotExist as err:
5052
cla.log.warning('Individual Signature - user ID was NOT found for: {}'.format(user_id))
5153
return {'errors': {'user_id': str(err)}}
52-
primary_user_email = user.get_user_email()
54+
55+
lf_username = user.get_lf_username()
56+
if lf_username:
57+
try:
58+
platform_users = UserService.get_users_by_username(lf_username) or []
59+
except Exception as err:
60+
cla.log.warning(
61+
f'Individual Signature - unable to fetch platform user by username: {lf_username}, error: {err}'
62+
)
63+
platform_users = []
64+
65+
for platform_user in platform_users:
66+
if not platform_user:
67+
continue
68+
for email in (platform_user.get('Emails') or []):
69+
if email and email.get('IsPrimary') and email.get('EmailAddress'):
70+
primary_user_email = email.get('EmailAddress')
71+
break
72+
if primary_user_email:
73+
break
74+
75+
if not primary_user_email:
76+
primary_user_email = next(iter(user.get_user_emails() or []), None)
77+
5378
return signing_service.request_individual_signature(str(project_id), str(user_id), return_url, return_url_type,
5479
preferred_email=primary_user_email)
5580

56-
5781
def request_corporate_signature(auth_user,
5882
project_id: str,
5983
company_id: str,

tests/functional/cypress/e2e/v2/signatures.cy.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ import { validate_200_Status, validate_expected_status, getAPIBaseURL } from '..
55

66
describe('To Validate & test Signature APIs via API call (V2)', function () {
77
const claEndpoint = getAPIBaseURL('v2');
8+
const environment = Cypress.env('CYPRESS_ENV');
9+
10+
let appConfig: any = {};
11+
if (environment === 'dev') {
12+
appConfig = require('../../appConfig/config.dev.ts').appConfig;
13+
} else if (environment === 'production') {
14+
appConfig = require('../../appConfig/config.production.ts').appConfig;
15+
}
16+
817
let allowFail: boolean = !(Cypress.env('ALLOW_FAIL') === 1);
918
const timeout = 180000;
1019

@@ -41,6 +50,38 @@ describe('To Validate & test Signature APIs via API call (V2)', function () {
4150
});
4251
});
4352

53+
describe('Configured request-individual-signature coverage', function () {
54+
before(function () {
55+
if (!appConfig.projectID || !appConfig.user_id) {
56+
this.skip();
57+
return;
58+
}
59+
});
60+
61+
it('POST /request-individual-signature - Request GitLab individual signature (No authentication required)', function () {
62+
const requestData = {
63+
project_id: appConfig.projectID,
64+
user_id: appConfig.user_id,
65+
return_url_type: 'Gitlab',
66+
return_url: 'https://gitlab.com/test/repo/-/merge_requests/1',
67+
};
68+
69+
cy.request({
70+
method: 'POST',
71+
url: `${claEndpoint}request-individual-signature`,
72+
timeout: timeout,
73+
failOnStatusCode: allowFail,
74+
body: requestData,
75+
}).then((response) => {
76+
return cy.logJson('POST /request-individual-signature (GitLab) response', response).then(() => {
77+
validate_200_Status(response);
78+
expect(response.body).to.be.an('object');
79+
// V2 API can return signature data or error object - both are valid
80+
});
81+
});
82+
});
83+
});
84+
4485
it('POST /request-employee-signature - Request employee signature (No authentication required)', function () {
4586
const requestData = {
4687
project_id: validProjectID,

tests/functional/cypress/e2e/v4/signatures.cy.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('To Validate & get list of signatures of ClaGroups via API call', funct
1515
const environment = Cypress.env('CYPRESS_ENV');
1616

1717
// Import the appropriate configuration based on the environment
18-
let appConfig;
18+
let appConfig: any = {};
1919
if (environment === 'dev') {
2020
appConfig = require('../../appConfig/config.dev.ts').appConfig;
2121
} else if (environment === 'production') {
@@ -50,7 +50,58 @@ describe('To Validate & get list of signatures of ClaGroups via API call', funct
5050
let bearerToken: string = null;
5151
const timeout = 180000;
5252

53-
before(() => {
53+
it('POST /request-individual-signature - Request GitHub individual signature (Go v4 path)', function () {
54+
55+
const requestData = {
56+
project_id: appConfig.projectID,
57+
user_id: appConfig.user_id,
58+
return_url_type: 'Github',
59+
return_url: 'https://github.com/test/repo/pull/1',
60+
};
61+
62+
cy.request({
63+
method: 'POST',
64+
url: `${claEndpoint}request-individual-signature`,
65+
timeout: timeout,
66+
failOnStatusCode: allowFail,
67+
body: requestData,
68+
}).then((response) => {
69+
return cy.logJson('POST /request-individual-signature (GitHub, v4) response', response).then(() => {
70+
validate_200_Status(response);
71+
expect(response.body).to.be.an('object');
72+
});
73+
});
74+
});
75+
76+
it('POST /request-individual-signature - Request GitLab individual signature (Go v4 path)', function () {
77+
78+
const requestData = {
79+
project_id: appConfig.projectID,
80+
user_id: appConfig.user_id,
81+
return_url_type: 'Gitlab',
82+
return_url: 'https://gitlab.com/test/repo/-/merge_requests/1',
83+
};
84+
85+
cy.request({
86+
method: 'POST',
87+
url: `${claEndpoint}request-individual-signature`,
88+
timeout: timeout,
89+
failOnStatusCode: allowFail,
90+
body: requestData,
91+
}).then((response) => {
92+
return cy.logJson('POST /request-individual-signature (GitLab, v4) response', response).then(() => {
93+
validate_200_Status(response);
94+
expect(response.body).to.be.an('object');
95+
});
96+
});
97+
});
98+
99+
before(function () {
100+
if (!appConfig.projectID || !appConfig.user_id) {
101+
this.skip();
102+
return;
103+
}
104+
54105
if (bearerToken == null) {
55106
getTokenKey(bearerToken);
56107
cy.window().then((win) => {

0 commit comments

Comments
 (0)