From 7615cf5b76401b2a6471a8aa5ced9799104c4e72 Mon Sep 17 00:00:00 2001 From: zvlb Date: Fri, 25 Oct 2024 15:54:35 +0300 Subject: [PATCH 1/3] Add logix for collect user project for Gitlab connector Signed-off-by: zvlb --- connector/gitlab/gitlab.go | 97 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go index 7aa4439842..9f20976d17 100644 --- a/connector/gitlab/gitlab.go +++ b/connector/gitlab/gitlab.go @@ -10,6 +10,7 @@ import ( "log/slog" "net/http" "strconv" + "sync" "time" "golang.org/x/oauth2" @@ -24,6 +25,10 @@ const ( // used to retrieve groups from /oauth/userinfo // https://docs.gitlab.com/ee/integration/openid_connect_provider.html scopeOpenID = "openid" + // used to get user projects from /api/v4/projects + scopeReadApi = "read_api" + // Min Access Level for Gitlab Propects + gitlabMinAccessLevel = 30 ) // Config holds configuration options for gitlab logins. @@ -35,6 +40,7 @@ type Config struct { Groups []string `json:"groups"` UseLoginAsID bool `json:"useLoginAsID"` GetGroupsPermission bool `json:"getGroupsPermission"` + GetProjects bool `json:"getProjects"` } type gitlabUser struct { @@ -46,6 +52,12 @@ type gitlabUser struct { IsAdmin bool } +type gitlabProjects struct { + ID int `json:"id"` + Name string `json:"name"` + PathWithNamespace string `json:"path_with_namespace"` +} + // Open returns a strategy for logging in through GitLab. func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) { if c.BaseURL == "" { @@ -60,6 +72,7 @@ func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, erro groups: c.Groups, useLoginAsID: c.UseLoginAsID, getGroupsPermission: c.GetGroupsPermission, + getProjects: c.GetProjects, }, nil } @@ -87,6 +100,9 @@ type gitlabConnector struct { // if set to true permissions will be added to list of groups getGroupsPermission bool + + // if set to true will get user projects + getProjects bool } func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { @@ -94,6 +110,9 @@ func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { if c.groupsRequired(scopes.Groups) { gitlabScopes = []string{scopeUser, scopeOpenID} } + if c.getProjects { + gitlabScopes = append(gitlabScopes, scopeReadApi) + } gitlabEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + "/oauth/authorize", TokenURL: c.baseURL + "/oauth/token"} return &oauth2.Config{ @@ -357,6 +376,14 @@ func (c *gitlabConnector) getGroups(ctx context.Context, client *http.Client, gr return nil, err } + if c.getProjects { + userProjects, err := c.getUserProjects(ctx, client) + if err != nil { + return nil, err + } + gitlabGroups = append(gitlabGroups, userProjects...) + } + if len(c.groups) > 0 { filteredGroups := groups.Filter(gitlabGroups, c.groups) if len(filteredGroups) == 0 { @@ -369,3 +396,73 @@ func (c *gitlabConnector) getGroups(ctx context.Context, client *http.Client, gr return nil, nil } + +func (c *gitlabConnector) getUserProjects(ctx context.Context, client *http.Client) ([]string, error) { + projects, totalPages, err := c.getUserProjectsPerPage(ctx, client, 1) + if err != nil { + return nil, err + } + if totalPages > 1 { + var wg sync.WaitGroup + limit := make(chan struct{}, 10) + for p := 2; p <= totalPages; p++ { + wg.Add(1) + limit <- struct{}{} + go func( + ctx context.Context, + client *http.Client, + p int, + ) { + defer func() { + wg.Done() + <-limit + }() + projectsPagination, _, _ := c.getUserProjectsPerPage(ctx, client, p) + projects = append(projects, projectsPagination...) + }(ctx, client, p) + } + wg.Wait() + } + + var projectsPath []string + for _, gp := range projects { + projectsPath = append(projectsPath, gp.PathWithNamespace) + } + + return projectsPath, nil +} +func (c *gitlabConnector) getUserProjectsPerPage(ctx context.Context, client *http.Client, page int) ([]gitlabProjects, int, error) { + var projects []gitlabProjects + + url := fmt.Sprintf("%s/api/v4/projects?min_access_level=%v&page=%+v", c.baseURL, gitlabMinAccessLevel, page) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, 0, fmt.Errorf("gitlab: new req: %v", err) + } + req = req.WithContext(ctx) + + resp, err := client.Do(req) + if err != nil { + return nil, 0, fmt.Errorf("gitlab: get URL %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, 0, fmt.Errorf("gitlab: read body: %v", err) + } + return nil, 0, fmt.Errorf("%s: %s", resp.Status, body) + } + + if err := json.NewDecoder(resp.Body).Decode(&projects); err != nil { + return nil, 0, fmt.Errorf("failed to decode response: %v", err) + } + + totalPages, err := strconv.Atoi(resp.Header["X-Total-Pages"][0]) + if err != nil { + return nil, 0, err + } + + return projects, totalPages, nil +} From 35f5826c718d3ec14319cc52299f0c86b734584c Mon Sep 17 00:00:00 2001 From: zvlb Date: Fri, 25 Oct 2024 16:26:16 +0300 Subject: [PATCH 2/3] Fix linter errors Signed-off-by: zvlb --- connector/gitlab/gitlab.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go index 9f20976d17..a2f02e336d 100644 --- a/connector/gitlab/gitlab.go +++ b/connector/gitlab/gitlab.go @@ -26,7 +26,7 @@ const ( // https://docs.gitlab.com/ee/integration/openid_connect_provider.html scopeOpenID = "openid" // used to get user projects from /api/v4/projects - scopeReadApi = "read_api" + scopeReadAPI = "read_api" // Min Access Level for Gitlab Propects gitlabMinAccessLevel = 30 ) @@ -424,7 +424,7 @@ func (c *gitlabConnector) getUserProjects(ctx context.Context, client *http.Clie wg.Wait() } - var projectsPath []string + projectsPath := make([]string, 0, len(projects)) for _, gp := range projects { projectsPath = append(projectsPath, gp.PathWithNamespace) } From d035a1b19875edce04160277e0a37995b9b53e05 Mon Sep 17 00:00:00 2001 From: zvlb Date: Fri, 25 Oct 2024 16:36:43 +0300 Subject: [PATCH 3/3] small fix Signed-off-by: zvlb --- connector/gitlab/gitlab.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go index a2f02e336d..7e725e30b3 100644 --- a/connector/gitlab/gitlab.go +++ b/connector/gitlab/gitlab.go @@ -111,7 +111,7 @@ func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { gitlabScopes = []string{scopeUser, scopeOpenID} } if c.getProjects { - gitlabScopes = append(gitlabScopes, scopeReadApi) + gitlabScopes = append(gitlabScopes, scopeReadAPI) } gitlabEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + "/oauth/authorize", TokenURL: c.baseURL + "/oauth/token"}