Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
afa4ef1
Added test coverage that compares '/vN/projects<uuid>' for N=2 and N=4
lukaszgryglicki Jul 8, 2025
b0bd950
Minor update
lukaszgryglicki Jul 8, 2025
03bb875
New API '/v4/project-compat/<uuid>' and its test coverage - including…
lukaszgryglicki Jul 9, 2025
05c5545
Update coverage
lukaszgryglicki Jul 9, 2025
3d19ae0
One more update
lukaszgryglicki Jul 9, 2025
ad701cd
Fix the test coverage
lukaszgryglicki Jul 9, 2025
b36f5a8
Address lint feedback
lukaszgryglicki Jul 9, 2025
4e4e80e
Added test suite to test against all dev projects
lukaszgryglicki Jul 10, 2025
3229554
Update lint
lukaszgryglicki Jul 10, 2025
b217458
Final fix that allows 'compat' behaviour of V4 API that fallbacks to …
lukaszgryglicki Jul 10, 2025
2cb7f8f
Merge pull request #4711 from linuxfoundation/unicron-port-py-apis-to-go
lukaszgryglicki Jul 10, 2025
af9859b
Update api docs comments
lukaszgryglicki Jul 15, 2025
43610f7
Port /v2/user/<user-uuid>/active-signature to golang
lukaszgryglicki Jul 16, 2025
861d430
WIP
lukaszgryglicki Jul 16, 2025
1f2f678
WIP
lukaszgryglicki Jul 16, 2025
ffb2092
API ported
lukaszgryglicki Jul 16, 2025
52655ee
API ported - fix to return null in exact same format as python
lukaszgryglicki Jul 16, 2025
2b8a2aa
API ported - address lint
lukaszgryglicki Jul 16, 2025
2b61d5b
Add test coverage for the new API /v4/user/<uuid>/active-signature vs…
lukaszgryglicki Jul 21, 2025
fe0b9f3
Test coverage updates
lukaszgryglicki Jul 21, 2025
622bfd1
Merge pull request #4714 from linuxfoundation/unicron-port-user-activ…
lukaszgryglicki Jul 21, 2025
0611598
Update test coverage
lukaszgryglicki Jul 22, 2025
4379843
Merge pull request #4716 from linuxfoundation/unicron-update-test-cov…
lukaszgryglicki Jul 22, 2025
fe03608
Add test coverage for non-v4 and invalid UUIDs and fix v4 APIs to beh…
lukaszgryglicki Jul 23, 2025
7200a0d
Merge pull request #4720 from linuxfoundation/uncron-active-signature…
lukaszgryglicki Jul 23, 2025
41346de
Port '/v2/user/<uuid>' py API to '/v3/user-compat/<uuid>' go API
lukaszgryglicki Jul 24, 2025
abac3f1
Update variable name as suggested by AI
lukaszgryglicki Jul 24, 2025
595050b
Merge pull request #4721 from linuxfoundation/unicron-port-user-api-t…
lukaszgryglicki Jul 24, 2025
159609d
Updates to cypress tests
lukaszgryglicki Jul 29, 2025
95533c8
Remove any sensitive data
lukaszgryglicki Jul 29, 2025
35d7437
Update README.md
lukaszgryglicki Jul 29, 2025
3c29882
Update the README.md file
lukaszgryglicki Jul 29, 2025
054a8c8
Merge pull request #4724 from linuxfoundation/unicron-cypress-readme
lukaszgryglicki Jul 29, 2025
aa098f1
Add support for whitelisting bots
lukaszgryglicki Jul 29, 2025
5fcf079
Fix Copilot suggestions
lukaszgryglicki Jul 29, 2025
07baaa4
Add documentation and updates to code
lukaszgryglicki Jul 30, 2025
e918790
Add documentation and updates to code 2
lukaszgryglicki Jul 30, 2025
6ca6b2e
Add golang version
lukaszgryglicki Jul 30, 2025
8cc692e
Address copilot review feedback
lukaszgryglicki Jul 30, 2025
5a9689c
Merge pull request #4725 from linuxfoundation/unicron-4701-allow-bots…
lukaszgryglicki Jul 30, 2025
31aa026
Add more visibility and fix one dminor debugging message in another f…
lukaszgryglicki Jul 31, 2025
03598e3
SPlit actor debugging info into a helper function
lukaszgryglicki Jul 31, 2025
b09977d
Merge pull request #4726 from linuxfoundation/unicron-4701-allow-bots…
lukaszgryglicki Jul 31, 2025
6b5e26c
Fix logic issue
lukaszgryglicki Jul 31, 2025
65a102b
Merge pull request #4727 from linuxfoundation/unicron-4701-allow-bots…
lukaszgryglicki Jul 31, 2025
f021e0f
Refactor to support array of configs, additional name pattern and mak…
lukaszgryglicki Jul 31, 2025
24d3547
Merge pull request #4728 from linuxfoundation/unicron-4701-allow-bots…
lukaszgryglicki Jul 31, 2025
93ac01c
Minor updates
lukaszgryglicki Jul 31, 2025
1c5dbae
One more docs update
lukaszgryglicki Jul 31, 2025
c8eceff
Clarify what login is used for vs username, account for GitHub bots (…
lukaszgryglicki Jul 31, 2025
e4a0d08
Merge pull request #4730 from linuxfoundation/unicron-update-skiping-…
lukaszgryglicki Jul 31, 2025
c6f5430
Quick fix
lukaszgryglicki Jul 31, 2025
7c628c3
Use inclusive naming 'whitelist' -> 'allowlist'
lukaszgryglicki Aug 1, 2025
016320b
Merge pull request #4732 from linuxfoundation/unicron-use-inclusive-n…
lukaszgryglicki Aug 1, 2025
370ffc0
Docs updated
lukaszgryglicki Aug 2, 2025
fb13f1b
Add support for patterns matching empty or missing properties: login,…
lukaszgryglicki Aug 2, 2025
2934132
Copilot AI review feedback
lukaszgryglicki Aug 2, 2025
c07826e
Fix the swagger
lukaszgryglicki Aug 2, 2025
233e864
Merge pull request #4734 from linuxfoundation/unicron-support-empty-p…
lukaszgryglicki Aug 2, 2025
4462a18
Merge branch 'main' into dev
lukaszgryglicki Aug 2, 2025
02f1796
Remove debugging code (even commented out)
lukaszgryglicki Aug 2, 2025
c56501b
Merge branch 'dev' of https://github.com/communitybridge/easycla into…
lukaszgryglicki Aug 2, 2025
4ee81ab
Fix logging multiple events and messages
lukaszgryglicki Aug 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,11 @@ cla-backend/run-python-test-example-*.py
out
*.secret
*log*.json

# Cypress test outputs
**/cypress/screenshots/
**/cypress/videos/
**/cypress/reports/

# Local env vars
.env
6 changes: 3 additions & 3 deletions cla-backend-go/auth/auth0.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Validator struct {
}

// NewAuthValidator creates a new auth0 validator based on the specified parameters
func NewAuthValidator(domain, clientID, usernameClaim, algorithm string) (Validator, error) { // nolint
func NewAuthValidator(domain, clientID, usernameClaim, nameClaim, emailClaim, algorithm string) (Validator, error) { // nolint
if domain == "" {
return Validator{}, errors.New("missing Domain")
}
Expand All @@ -43,8 +43,8 @@ func NewAuthValidator(domain, clientID, usernameClaim, algorithm string) (Valida
usernameClaim: usernameClaim,
algorithm: algorithm,
wellKnownURL: "https://" + path.Join(domain, ".well-known/jwks.json"),
nameClaim: "name",
emailClaim: "email",
nameClaim: nameClaim,
emailClaim: emailClaim,
}

return validator, nil
Expand Down
19 changes: 12 additions & 7 deletions cla-backend-go/cmd/response_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package cmd

import (
"sync"
"time"

"github.com/linuxfoundation/easycla/cla-backend-go/utils"
Expand All @@ -18,32 +19,36 @@ type responseMetrics struct {
expire time.Time
}

var reqMap = make(map[string]*responseMetrics, 5)
var reqMap sync.Map

// requestStart holds the request ID, method and timing information in a small structure
func requestStart(reqID, method string) {
now, _ := utils.CurrentTime()
reqMap[reqID] = &responseMetrics{
rm := &responseMetrics{
reqID: reqID,
method: method,
start: now,
elapsed: 0,
expire: now.Add(time.Minute * 5),
}
reqMap.Store(reqID, rm)
}

// getRequestMetrics returns the response metrics based on the request id value
func getRequestMetrics(reqID string) *responseMetrics {
if x, found := reqMap[reqID]; found {
if val, found := reqMap.Load(reqID); found {
rm, ok := val.(*responseMetrics)
if !ok {
return nil
}
now, _ := utils.CurrentTime()
x.elapsed = now.Sub(x.start)
return x
rm.elapsed = now.Sub(rm.start)
return rm
}

return nil
}

// clearRequestMetrics removes the request from the map
func clearRequestMetrics(reqID string) {
delete(reqMap, reqID)
reqMap.Delete(reqID)
}
17 changes: 15 additions & 2 deletions cla-backend-go/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,24 @@ func server(localMode bool) http.Handler {
}

// LG: to test with manual tokens
// configFile.Auth0.UsernameClaim = "http://lfx.dev/claims/username"
customClaimUsername := os.Getenv("AUTH0_USERNAME_CLAIM_CLI")
if customClaimUsername != "" {
configFile.Auth0.UsernameClaim = customClaimUsername
}
nameClaimName := os.Getenv("AUTH0_NAME_CLAIM_CLI")
if nameClaimName == "" {
nameClaimName = "name"
}
emailClaimName := os.Getenv("AUTH0_EMAIL_CLAIM_CLI")
if emailClaimName == "" {
emailClaimName = "email"
}
authValidator, err := auth.NewAuthValidator(
configFile.Auth0.Domain,
configFile.Auth0.ClientID,
configFile.Auth0.UsernameClaim,
nameClaimName,
emailClaimName,
configFile.Auth0.Algorithm)
if err != nil {
logrus.Panic(err)
Expand Down Expand Up @@ -336,7 +349,7 @@ func server(localMode bool) http.Handler {
// Setup our API handlers
users.Configure(api, usersService, eventsService)
project.Configure(api, v1ProjectService, eventsService, gerritService, v1RepositoriesService, v1SignaturesService)
v2Project.Configure(v2API, v1ProjectService, v2ProjectService, eventsService)
v2Project.Configure(v2API, v1ProjectService, v2ProjectService, eventsService, v1ProjectClaGroupService, v2RepositoriesService, gerritService)
health.Configure(api, healthService)
v2Health.Configure(v2API, healthService)
template.Configure(api, templateService, eventsService)
Expand Down
34 changes: 19 additions & 15 deletions cla-backend-go/github/bots.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,29 +222,33 @@ func SkipAllowlistedBots(ev events.Service, orgModel *models.GithubOrganization,
}
log.WithFields(f).Debugf("final skip_cla config for repo %s is %+v; actorsMissingCLA: [%s]", orgRepo, configArray, strings.Join(actorDebugData, ", "))

seenActors := make(map[string]struct{})
for _, actor := range actorsMissingCLA {
if actor == nil {
continue
}
actorData := actorToString(actor)
log.WithFields(f).Debugf("Checking actor: %s for skip_cla config: %+v", actorData, configArray)
if isActorSkipped(actor, configArray) {
msg := fmt.Sprintf(
"Skipping CLA check for repo='%s', actor: %s due to skip_cla config: %+v",
orgRepo, actorData, configArray,
)
log.WithFields(f).Info(msg)
eventData := events.BypassCLAEventData{
Repo: orgRepo,
Config: config,
Actor: actorData,
_, seen := seenActors[actorData]
if !seen {
seenActors[actorData] = struct{}{}
msg := fmt.Sprintf(
"Skipping CLA check for repo='%s', actor: %s due to skip_cla config: %+v",
orgRepo, actorData, configArray,
)
log.WithFields(f).Info(msg)
eventData := events.BypassCLAEventData{
Repo: orgRepo,
Config: config,
Actor: actorData,
}
ev.LogEvent(&events.LogEventArgs{
EventType: events.BypassCLA,
EventData: &eventData,
ProjectID: projectID,
})
}
ev.LogEvent(&events.LogEventArgs{
EventType: events.BypassCLA,
EventData: &eventData,
ProjectID: projectID,
})
log.WithFields(f).Debugf("event logged")
actor.Authorized = true
allowlistedActors = append(allowlistedActors, actor)
} else {
Expand Down
15 changes: 15 additions & 0 deletions cla-backend-go/project/mocks/mock_repo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions cla-backend-go/project/mocks/mock_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 22 additions & 4 deletions cla-backend-go/project/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
type ProjectRepository interface { //nolint
CreateCLAGroup(ctx context.Context, claGroupModel *models.ClaGroup) (*models.ClaGroup, error)
GetCLAGroupByID(ctx context.Context, claGroupID string, loadRepoDetails bool) (*models.ClaGroup, error)
GetCLAGroupByIDCompat(ctx context.Context, claGroupID string, loadRepoDetails bool) (*models.ClaGroup, error)
GetCLAGroupsByExternalID(ctx context.Context, params *project.GetProjectsByExternalIDParams, loadRepoDetails bool) (*models.ClaGroups, error)
GetCLAGroupByName(ctx context.Context, claGroupName string) (*models.ClaGroup, error)
GetExternalCLAGroup(ctx context.Context, claGroupExternalID string) (*models.ClaGroup, error)
Expand Down Expand Up @@ -149,7 +150,7 @@ func (repo *repo) CreateCLAGroup(ctx context.Context, claGroupModel *models.ClaG
return claGroupModel, nil
}

func (repo *repo) getCLAGroupByID(ctx context.Context, claGroupID string, loadCLAGroupDetails bool) (*models.ClaGroup, error) {
func (repo *repo) getCLAGroupByID(ctx context.Context, claGroupID string, loadCLAGroupDetails bool, claEnabledDefaultIsTrue bool) (*models.ClaGroup, error) {
f := logrus.Fields{
"functionName": "project.repository.getCLAGroupByID",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
Expand Down Expand Up @@ -188,19 +189,36 @@ func (repo *repo) getCLAGroupByID(ctx context.Context, claGroupID string, loadCL
return nil, &utils.CLAGroupNotFound{CLAGroupID: claGroupID}
}
var dbModel models2.DBProjectModel
err = dynamodbattribute.UnmarshalMap(results.Items[0], &dbModel)
rawItem := results.Items[0]
err = dynamodbattribute.UnmarshalMap(rawItem, &dbModel)
if err != nil {
log.WithFields(f).Warnf("error unmarshalling db cla group model, error: %+v", err)
return nil, err
}
if claEnabledDefaultIsTrue {
// If missing, assume true like Pynamo default=True
if _, ok := rawItem["project_icla_enabled"]; !ok {
dbModel.ProjectIclaEnabled = true
}
if _, ok := rawItem["project_ccla_enabled"]; !ok {
dbModel.ProjectCclaEnabled = true
}
}

// Convert the database model to an API response model
return repo.buildCLAGroupModel(ctx, dbModel, loadCLAGroupDetails), nil
}

// GetCLAGroupByID returns the cla group model associated for the specified claGroupID
func (repo *repo) GetCLAGroupByID(ctx context.Context, claGroupID string, loadRepoDetails bool) (*models.ClaGroup, error) {
return repo.getCLAGroupByID(ctx, claGroupID, loadRepoDetails)
return repo.getCLAGroupByID(ctx, claGroupID, loadRepoDetails, false)
}

// GetCLAGroupByIDCompat returns the cla group model associated for the specified claGroupID
func (repo *repo) GetCLAGroupByIDCompat(ctx context.Context, claGroupID string, loadRepoDetails bool) (*models.ClaGroup, error) {
// Uses compatible mode (with python v2): claEnabledDefaultIsTrue - means if project_ccla_enabled or project_icla_enabled
// aren't set on dynamoDB item - they will default to true as in Py V2 API
return repo.getCLAGroupByID(ctx, claGroupID, loadRepoDetails, true)
}

// GetCLAGroupsByExternalID queries the database and returns a list of the cla groups
Expand Down Expand Up @@ -383,7 +401,7 @@ func (repo *repo) GetClaGroupByProjectSFID(ctx context.Context, projectSFID stri

log.WithFields(f).Debugf("found CLA Group ID: %s for project SFID: %s", claGroupProject.ClaGroupID, projectSFID)

return repo.getCLAGroupByID(ctx, claGroupProject.ClaGroupID, loadRepoDetails)
return repo.getCLAGroupByID(ctx, claGroupProject.ClaGroupID, loadRepoDetails, false)
}

// GetCLAGroupByName returns the project model associated for the specified project name
Expand Down
21 changes: 20 additions & 1 deletion cla-backend-go/project/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Service interface {
CreateCLAGroup(ctx context.Context, project *models.ClaGroup) (*models.ClaGroup, error)
GetCLAGroups(ctx context.Context, params *project.GetProjectsParams) (*models.ClaGroups, error)
GetCLAGroupByID(ctx context.Context, claGroupID string) (*models.ClaGroup, error)
GetCLAGroupByIDCompat(ctx context.Context, claGroupID string) (*models.ClaGroup, error)
GetCLAGroupsByExternalSFID(ctx context.Context, projectSFID string) (*models.ClaGroups, error)
GetCLAGroupsByExternalID(ctx context.Context, params *project.GetProjectsByExternalIDParams) (*models.ClaGroups, error)
GetCLAGroupByName(ctx context.Context, projectName string) (*models.ClaGroup, error)
Expand Down Expand Up @@ -75,6 +76,16 @@ func (s ProjectService) GetCLAGroups(ctx context.Context, params *project.GetPro

// GetCLAGroupByID service method
func (s ProjectService) GetCLAGroupByID(ctx context.Context, claGroupID string) (*models.ClaGroup, error) {
return s.getCLAGroupByID(ctx, claGroupID, false)
}

// GetCLAGroupByIDCompat service method
func (s ProjectService) GetCLAGroupByIDCompat(ctx context.Context, claGroupID string) (*models.ClaGroup, error) {
return s.getCLAGroupByID(ctx, claGroupID, true)
}

// getCLAGroupByID service method
func (s ProjectService) getCLAGroupByID(ctx context.Context, claGroupID string, claEnabledDefaultIsTrue bool) (*models.ClaGroup, error) {
f := logrus.Fields{
"functionName": "GetCLAGroupByID",
utils.XREQUESTID: ctx.Value(utils.XREQUESTID),
Expand All @@ -83,7 +94,15 @@ func (s ProjectService) GetCLAGroupByID(ctx context.Context, claGroupID string)
}

log.WithFields(f).Debug("locating CLA Group by ID...")
project, err := s.repo.GetCLAGroupByID(ctx, claGroupID, repository.LoadRepoDetails)
var (
project *models.ClaGroup
err error
)
if claEnabledDefaultIsTrue {
project, err = s.repo.GetCLAGroupByIDCompat(ctx, claGroupID, repository.LoadRepoDetails)
} else {
project, err = s.repo.GetCLAGroupByID(ctx, claGroupID, repository.LoadRepoDetails)
}
if err != nil {
return nil, err
}
Expand Down
40 changes: 40 additions & 0 deletions cla-backend-go/swagger/cla.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,34 @@ paths:
tags:
- users

/user-compat/{userID}:
parameters:
- $ref: "#/parameters/x-request-id"
- $ref: "#/parameters/userPathUuid"
get:
summary: Get user by ID (returns data in the same format as Py V2 API)
security: [ ]
operationId: getUserCompat
responses:
'200':
description: 'Success'
headers:
x-request-id:
type: string
description: The unique request ID value - assigned/set by the API Gateway based on the session
schema:
$ref: '#/definitions/user-compat'
'400':
$ref: '#/responses/invalid-request'
'401':
$ref: '#/responses/unauthorized'
'403':
$ref: '#/responses/forbidden'
'404':
$ref: '#/responses/not-found'
tags:
- users

/users/{userID}:
get:
summary: Get User by ID
Expand Down Expand Up @@ -2429,6 +2457,13 @@ paths:

# Common parameters
parameters:
userPathUuid:
name: userID
in: path
required: true
description: The user ID (UUID string)
type: string
pattern: '^[a-fA-F0-9]{8}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{4}-?[a-fA-F0-9]{12}$' # this is any UUID, not only v4
x-request-id:
name: X-REQUEST-ID
description: The unique request ID value - assigned/set by the API Gateway based on the login session
Expand Down Expand Up @@ -2677,6 +2712,9 @@ definitions:
user:
$ref: './common/user.yaml'

user-compat:
$ref: './common/user-compat.yaml'

user-update:
type: object
title: User
Expand All @@ -2690,6 +2728,8 @@ definitions:
type: string
lfEmail:
type: string
lfSub:
type: string
lfUsername:
type: string
companyID:
Expand Down
Loading
Loading