Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OIDC authentication customizer for GitLab #1052

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ff4d92f
feat: initial GitLab integration (WIP)
jhoward-lm Feb 12, 2025
d5e2679
fix: added gitlab auth class
pkwiatkowski1 Feb 12, 2025
a1a1035
fixed pom merge issue
pkwiatkowski1 Feb 12, 2025
b52c077
refactor: implement customizer, register event
jhoward-lm Feb 12, 2025
0c81541
refactor: remove token class member from sync task
jhoward-lm Feb 12, 2025
8669568
refactor: make user available to syncer
jhoward-lm Feb 12, 2025
c44b77b
fix: remove scheduled task registration
jhoward-lm Feb 12, 2025
7b29e8b
style: javadoc list
jhoward-lm Feb 12, 2025
ed4486e
refactor: use Event.dispatch, remove pom repository
jhoward-lm Feb 12, 2025
9d28cea
chore: fix pom alpine-parent version and formatting
jhoward-lm Feb 12, 2025
64ee9f0
fix: add migration changeset
jhoward-lm Feb 12, 2025
5844fbe
added user as syncer class var
pkwiatkowski1 Feb 12, 2025
9637c34
refactor: add GitLabRole enum
jhoward-lm Feb 12, 2025
799ceb1
fix: remove redundant superinterface
jhoward-lm Feb 12, 2025
7ccf3ac
fix: ensure query manager instantiated
jhoward-lm Feb 12, 2025
68a1268
fix: implement base Event interface
jhoward-lm Feb 12, 2025
0f65d81
feat: add GitLab Project Class and GitLab graphql query functionality
ashearin Feb 12, 2025
9c321c2
chore: formatting and visibility fixes
ashearin Feb 12, 2025
d8e6f19
add map permission to gitlab default roles
EphraimEM Feb 12, 2025
11cec33
add GitlabClientTest file
EphraimEM Feb 12, 2025
e056cfc
modify permission to static member of the class
EphraimEM Feb 12, 2025
5788010
supress warning on mapPermissionToRoles set to uncheck
EphraimEM Feb 12, 2025
c18b124
clean up remove unused import
EphraimEM Feb 12, 2025
eaeef42
add the planner role with permission
EphraimEM Feb 12, 2025
f8ff2d4
chore: refine gitlabsyncer class variables
ashearin Feb 12, 2025
f553daa
refactor: flatten created project structure
jhoward-lm Feb 12, 2025
7b12ec0
fix: add user to GitLab role teams
jhoward-lm Feb 12, 2025
0c144fe
tests: add gitlabsyncer tests
ashearin Feb 13, 2025
a53ca68
style: use modern Java conventions
jhoward-lm Feb 13, 2025
47f4795
perf: exclude archived GitLab projects
jhoward-lm Feb 13, 2025
4406575
refactor: configurable GraphQL query inputs
jhoward-lm Feb 13, 2025
8c489de
chore: move baseURL initialization to gitlab client constructor
ashearin Feb 13, 2025
e5a18dc
chore: remove duplicate accesstoken member
ashearin Feb 13, 2025
f952260
refactor: locks to manage concurrent project access
jhoward-lm Feb 17, 2025
4f2ede7
Merge branch 'main' of https://github.com/DependencyTrack/hyades-apis…
jhoward-lm Feb 24, 2025
1a0f052
add getGitLabProjects unit test
lmphil Feb 24, 2025
cc785c8
refactor: remove GitLabProject name field
jhoward-lm Feb 24, 2025
265b9a1
refactor: add GitLab integration constants
jhoward-lm Feb 24, 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
9 changes: 8 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<parent>
<groupId>us.springett</groupId>
<artifactId>alpine-parent</artifactId>
<version>3.1.2</version>
<version>3.2.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
Expand Down Expand Up @@ -94,6 +94,7 @@
<lib.owasp-rr-calculator.version>1.0.1</lib.owasp-rr-calculator.version>
<lib.cyclonedx-java.version>9.1.0</lib.cyclonedx-java.version>
<lib.datanucleus-postgresql.version>0.3.0</lib.datanucleus-postgresql.version>
<lib.gitlab4j-api.version>6.0.0-rc.8</lib.gitlab4j-api.version>
<lib.jaxb.runtime.version>4.0.5</lib.jaxb.runtime.version>
<!--
Downgraded from 2.18.2 due to incompatibility with swagger-core.
Expand Down Expand Up @@ -555,6 +556,12 @@
<version>${lib.awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.gitlab4j</groupId>
<artifactId>gitlab4j-api</artifactId>
<version>${lib.gitlab4j-api.version}</version>
<type>module</type>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.dependencytrack.tasks.EpssMirrorTask;
import org.dependencytrack.tasks.FortifySscUploadTask;
import org.dependencytrack.tasks.GitHubAdvisoryMirrorTask;
import org.dependencytrack.tasks.GitLabSyncTask;
import org.dependencytrack.tasks.IntegrityAnalysisTask;
import org.dependencytrack.tasks.IntegrityMetaInitializerTask;
import org.dependencytrack.tasks.InternalComponentIdentificationTask;
Expand Down Expand Up @@ -96,6 +97,7 @@ public void contextInitialized(final ServletContextEvent event) {
EVENT_SERVICE.subscribe(VexUploadEvent.class, VexUploadProcessingTask.class);
EVENT_SERVICE.subscribe(LdapSyncEvent.class, LdapSyncTaskWrapper.class);
EVENT_SERVICE.subscribe(GitHubAdvisoryMirrorEvent.class, GitHubAdvisoryMirrorTask.class);
EVENT_SERVICE.subscribe(GitLabSyncEvent.class, GitLabSyncTask.class);
EVENT_SERVICE.subscribe(OsvMirrorEvent.class, OsvMirrorTask.class);
EVENT_SERVICE.subscribe(ProjectVulnerabilityAnalysisEvent.class, VulnerabilityAnalysisTask.class);
EVENT_SERVICE.subscribe(PortfolioVulnerabilityAnalysisEvent.class, VulnerabilityAnalysisTask.class);
Expand Down Expand Up @@ -143,6 +145,7 @@ public void contextDestroyed(final ServletContextEvent event) {
EVENT_SERVICE.unsubscribe(VexUploadProcessingTask.class);
EVENT_SERVICE.unsubscribe(LdapSyncTaskWrapper.class);
EVENT_SERVICE.unsubscribe(GitHubAdvisoryMirrorTask.class);
EVENT_SERVICE.unsubscribe(GitLabSyncTask.class);
EVENT_SERVICE.unsubscribe(OsvMirrorTask.class);
EVENT_SERVICE.unsubscribe(VulnerabilityAnalysisTask.class);
EVENT_SERVICE.unsubscribe(RepositoryMetaAnalysisTask.class);
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/org/dependencytrack/event/GitLabSyncEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.event;

import alpine.event.framework.Event;
import alpine.model.OidcUser;

/**
* Defines an event used to start a sync task of current user's GitLab groups.
*
* @author Jonathan Howard
*/
public class GitLabSyncEvent implements Event {

private String accessToken;
private OidcUser user;

public GitLabSyncEvent() {

}

public GitLabSyncEvent(final String accessToken, final OidcUser user) {
this.accessToken = accessToken;
this.user = user;
}

public String getAccessToken() {
return accessToken;
}

public void setAccessToken(final String accessToken) {
this.accessToken = accessToken;
}

public OidcUser getUser() {
return user;
}

public void setUser(OidcUser user) {
this.user = user;
}

@Override
public String toString() {
return "%s{accessToken=%s, user=%s}".formatted(getClass().getSimpleName(), accessToken, user);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.integrations;

import org.dependencytrack.persistence.QueryManager;

public interface PermissionsSyncer extends IntegrationPoint {

boolean isEnabled();

void setQueryManager(QueryManager qm);

void synchronize();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.integrations.gitlab;

import alpine.event.framework.Event;
import alpine.model.OidcUser;
import alpine.server.auth.DefaultOidcAuthenticationCustomizer;
import alpine.server.auth.OidcProfile;

import java.util.ArrayList;
import java.util.List;

import org.dependencytrack.event.GitLabSyncEvent;
import org.dependencytrack.persistence.QueryManager;

public class GitLabAuthenticationCustomizer extends DefaultOidcAuthenticationCustomizer {

public GitLabAuthenticationCustomizer() {

Check notice on line 34 in src/main/java/org/dependencytrack/integrations/gitlab/GitLabAuthenticationCustomizer.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/java/org/dependencytrack/integrations/gitlab/GitLabAuthenticationCustomizer.java#L34

Avoid unnecessary constructors - the compiler will generate these for you

}

@Override
public boolean isProfileComplete(OidcProfile profile, boolean teamSyncEnabled) {
return super.isProfileComplete(profile, true);
}

@Override
public OidcUser onAuthenticationSuccess(OidcUser user, OidcProfile profile, String idToken, String accessToken) {
try (QueryManager qm = new QueryManager()) {
List<String> groups = profile.getGroups();
groups = groups != null ? groups : new ArrayList<String>();

groups.stream()
.filter(groupName -> groupName == null)
.map(groupName -> qm.createOidcGroup(groupName));
}

Event.dispatch(new GitLabSyncEvent(accessToken, user));

return user;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/

package org.dependencytrack.integrations.gitlab;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.common.HttpClientPool;

import alpine.Config;
import alpine.common.logging.Logger;

import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
import net.minidev.json.JSONValue;

import static org.apache.commons.io.IOUtils.resourceToString;

public class GitLabClient {

private static final Logger LOGGER = Logger.getLogger(GitLabClient.class);

Check warning on line 52 in src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java#L52

Avoid unused private fields such as 'LOGGER'.
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

Check warning on line 53 in src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java#L53

Avoid unused private fields such as 'DATE_FORMAT'.
private static final String GRAPHQL_ENDPOINT = "/api/graphql";

private final String accessToken;
private final URI baseURL;
private final Config config;

Check warning on line 58 in src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java#L58

Avoid unused private fields such as 'config'.

Check warning on line 58 in src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/java/org/dependencytrack/integrations/gitlab/GitLabClient.java#L58

Perhaps 'config' could be replaced by a local variable.

private final Map<GitLabRole, List<Permissions>> rolePermissions = Map.of(
GitLabRole.GUEST, List.of(
Permissions.VIEW_PORTFOLIO,
Permissions.VIEW_VULNERABILITY,
Permissions.VIEW_BADGES),
GitLabRole.PLANNER, List.of(
Permissions.VIEW_PORTFOLIO,
Permissions.VIEW_VULNERABILITY,
Permissions.VIEW_POLICY_VIOLATION,
Permissions.VIEW_BADGES),
GitLabRole.REPORTER, List.of(
Permissions.VIEW_PORTFOLIO,
Permissions.VIEW_VULNERABILITY,
Permissions.VIEW_POLICY_VIOLATION,
Permissions.VIEW_BADGES),
GitLabRole.DEVELOPER, List.of(
Permissions.BOM_UPLOAD,
Permissions.VIEW_PORTFOLIO,
Permissions.PORTFOLIO_MANAGEMENT_READ,
Permissions.VIEW_VULNERABILITY,
Permissions.VULNERABILITY_ANALYSIS_READ,
Permissions.PROJECT_CREATION_UPLOAD),
GitLabRole.MAINTAINER, List.of(
Permissions.BOM_UPLOAD,
Permissions.PORTFOLIO_MANAGEMENT,
Permissions.PORTFOLIO_MANAGEMENT_CREATE,
Permissions.PORTFOLIO_MANAGEMENT_READ,
Permissions.PORTFOLIO_MANAGEMENT_UPDATE,
Permissions.PORTFOLIO_MANAGEMENT_DELETE,
Permissions.VULNERABILITY_ANALYSIS,
Permissions.VULNERABILITY_ANALYSIS_CREATE,
Permissions.VULNERABILITY_ANALYSIS_READ,
Permissions.VULNERABILITY_ANALYSIS_UPDATE,
Permissions.POLICY_MANAGEMENT,
Permissions.POLICY_MANAGEMENT_CREATE,
Permissions.POLICY_MANAGEMENT_READ,
Permissions.POLICY_MANAGEMENT_UPDATE,
Permissions.POLICY_MANAGEMENT_DELETE),
GitLabRole.OWNER, List.of(
Permissions.ACCESS_MANAGEMENT,
Permissions.ACCESS_MANAGEMENT_CREATE,
Permissions.ACCESS_MANAGEMENT_READ,
Permissions.ACCESS_MANAGEMENT_UPDATE,
Permissions.ACCESS_MANAGEMENT_DELETE,
Permissions.SYSTEM_CONFIGURATION,
Permissions.SYSTEM_CONFIGURATION_CREATE,
Permissions.SYSTEM_CONFIGURATION_READ,
Permissions.SYSTEM_CONFIGURATION_UPDATE,
Permissions.SYSTEM_CONFIGURATION_DELETE,
Permissions.TAG_MANAGEMENT,
Permissions.TAG_MANAGEMENT_DELETE));

public GitLabClient(final String accessToken) {
this(accessToken, Config.getInstance());
}

public GitLabClient(final String accessToken, final Config config) {
this.config = config;
this.accessToken = accessToken;
this.baseURL = URI.create(config.getProperty(Config.AlpineKey.OIDC_ISSUER));
}

public List<GitLabProject> getGitLabProjects() throws IOException, URISyntaxException {
List<GitLabProject> projects = new ArrayList<>();

JSONObject variables = new JSONObject();
JSONObject queryObject = new JSONObject();

queryObject.put("query", resourceToString("/graphql/gitlab-projects.graphql", StandardCharsets.UTF_8));

URIBuilder builder = new URIBuilder(baseURL.toString()).setPath(GRAPHQL_ENDPOINT);

HttpPost request = new HttpPost(builder.build());
request.setHeader("Authorization", "Bearer " + accessToken);
request.setHeader("Content-Type", "application/json");

while (true) {
queryObject.put("variables", variables);

StringEntity entity = new StringEntity(queryObject.toString(), StandardCharsets.UTF_8);
request.setEntity(entity);

try (CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) {
HttpEntity responseEntity = response.getEntity();

if (responseEntity == null)
break;

String responseBody = EntityUtils.toString(responseEntity);
JSONObject responseData = JSONValue.parse(responseBody, JSONObject.class);
JSONObject dataObject = (JSONObject) responseData.get("data");
JSONObject projectsObject = (JSONObject) dataObject.get("projects");
JSONArray nodes = (JSONArray) projectsObject.get("nodes");

for (Object nodeObject : nodes) {
JSONObject node = (JSONObject) nodeObject;
projects.add(GitLabProject.parse(node.toJSONString()));
}

JSONObject pageInfo = (JSONObject) projectsObject.get("pageInfo");

if (!(boolean) pageInfo.get("hasNextPage"))
break;

variables.put("cursor", pageInfo.getAsString("endCursor"));
}
}

return projects;
}

public List<Permissions> getRolePermissions(final GitLabRole role) {
return rolePermissions.get(role);
}

// JSONArray to ArrayList simple converter
public ArrayList<String> jsonToList(final JSONArray jsonArray) {
ArrayList<String> list = new ArrayList<>();

for (Object o : jsonArray != null ? jsonArray : Collections.emptyList())
list.add(o.toString());

return list;
}

}
Loading