Skip to content

Commit ab5fc40

Browse files
Add Repository Bearer Authentication
Signed-off-by: Valentijn Scholten <valentijnscholten@gmail.com>
1 parent ee5cbce commit ab5fc40

File tree

16 files changed

+219
-71
lines changed

16 files changed

+219
-71
lines changed

src/main/java/org/dependencytrack/model/Repository.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ public class Repository implements Serializable {
100100
@Column(name = "PASSWORD")
101101
private String password;
102102

103+
@Persistent
104+
@Column(name = "BEARERTOKEN")
105+
private String bearerToken;
106+
103107
@Persistent(customValueStrategy = "uuid")
104108
@Index(name = "REPOSITORY_UUID_IDX") // Cannot be @Unique. Microsoft SQL Server throws an exception
105109
@Column(name = "UUID", jdbcType = "VARCHAR", length = 36, allowsNull = "true")
@@ -189,6 +193,16 @@ public void setPassword(String password) {
189193
this.password = password;
190194
}
191195

196+
@JsonIgnore
197+
public String getBearerToken() {
198+
return bearerToken;
199+
}
200+
201+
@JsonProperty(value = "bearerToken")
202+
public void setBearerToken(String bearerToken) {
203+
this.bearerToken = bearerToken;
204+
}
205+
192206
public UUID getUuid() {
193207
return uuid;
194208
}

src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -214,23 +214,23 @@ private List<Permission> getBadgesPermissions(final List<Permission> fullList) {
214214
public void loadDefaultRepositories() {
215215
try (QueryManager qm = new QueryManager()) {
216216
LOGGER.info("Synchronizing default repositories to datastore");
217-
qm.createRepository(RepositoryType.CPAN, "cpan-public-registry", "https://fastapi.metacpan.org/v1/", true, false, false, null, null);
218-
qm.createRepository(RepositoryType.GEM, "rubygems.org", "https://rubygems.org/", true, false, false, null, null);
219-
qm.createRepository(RepositoryType.HEX, "hex.pm", "https://hex.pm/", true, false, false, null, null);
220-
qm.createRepository(RepositoryType.HACKAGE, "hackage.haskell.org", "https://hackage.haskell.org/", true, false, false, null, null);
221-
qm.createRepository(RepositoryType.MAVEN, "central", "https://repo1.maven.org/maven2/", true, false, false, null, null);
222-
qm.createRepository(RepositoryType.MAVEN, "atlassian-public", "https://packages.atlassian.com/content/repositories/atlassian-public/", true, false, false, null, null);
223-
qm.createRepository(RepositoryType.MAVEN, "jboss-releases", "https://repository.jboss.org/nexus/content/repositories/releases/", true, false, false, null, null);
224-
qm.createRepository(RepositoryType.MAVEN, "clojars", "https://repo.clojars.org/", true, false, false, null, null);
225-
qm.createRepository(RepositoryType.MAVEN, "google-android", "https://maven.google.com/", true, false, false, null, null);
226-
qm.createRepository(RepositoryType.NIXPKGS, "nixpkgs-unstable", "https://channels.nixos.org/nixpkgs-unstable/packages.json.br", true, false, false, null, null);
227-
qm.createRepository(RepositoryType.NPM, "npm-public-registry", "https://registry.npmjs.org/", true, false, false, null, null);
228-
qm.createRepository(RepositoryType.PYPI, "pypi.org", "https://pypi.org/", true, false, false, null, null);
229-
qm.createRepository(RepositoryType.NUGET, "nuget-gallery", "https://api.nuget.org/", true, false, false, null, null);
230-
qm.createRepository(RepositoryType.COMPOSER, "packagist", "https://repo.packagist.org/", true, false, false, null, null);
231-
qm.createRepository(RepositoryType.CARGO, "crates.io", "https://crates.io", true, false, false, null, null);
232-
qm.createRepository(RepositoryType.GO_MODULES, "proxy.golang.org", "https://proxy.golang.org", true, false, false, null, null);
233-
qm.createRepository(RepositoryType.GITHUB, "github.com", "https://github.com", true, false, false, null, null);
217+
qm.createRepository(RepositoryType.CPAN, "cpan-public-registry", "https://fastapi.metacpan.org/v1/", true, false, false, null, null, null);
218+
qm.createRepository(RepositoryType.GEM, "rubygems.org", "https://rubygems.org/", true, false, false, null, null, null);
219+
qm.createRepository(RepositoryType.HEX, "hex.pm", "https://hex.pm/", true, false, false, null, null, null);
220+
qm.createRepository(RepositoryType.HACKAGE, "hackage.haskell.org", "https://hackage.haskell.org/", true, false, false, null, null, null);
221+
qm.createRepository(RepositoryType.MAVEN, "central", "https://repo1.maven.org/maven2/", true, false, false, null, null, null);
222+
qm.createRepository(RepositoryType.MAVEN, "atlassian-public", "https://packages.atlassian.com/content/repositories/atlassian-public/", true, false, false, null, null, null);
223+
qm.createRepository(RepositoryType.MAVEN, "jboss-releases", "https://repository.jboss.org/nexus/content/repositories/releases/", true, false, false, null, null, null);
224+
qm.createRepository(RepositoryType.MAVEN, "clojars", "https://repo.clojars.org/", true, false, false, null, null, null);
225+
qm.createRepository(RepositoryType.MAVEN, "google-android", "https://maven.google.com/", true, false, false, null, null, null);
226+
qm.createRepository(RepositoryType.NIXPKGS, "nixpkgs-unstable", "https://channels.nixos.org/nixpkgs-unstable/packages.json.br", true, false, false, null, null, null);
227+
qm.createRepository(RepositoryType.NPM, "npm-public-registry", "https://registry.npmjs.org/", true, false, false, null, null, null);
228+
qm.createRepository(RepositoryType.PYPI, "pypi.org", "https://pypi.org/", true, false, false, null, null, null);
229+
qm.createRepository(RepositoryType.NUGET, "nuget-gallery", "https://api.nuget.org/", true, false, false, null, null, null);
230+
qm.createRepository(RepositoryType.COMPOSER, "packagist", "https://repo.packagist.org/", true, false, false, null, null, null);
231+
qm.createRepository(RepositoryType.CARGO, "crates.io", "https://crates.io", true, false, false, null, null, null);
232+
qm.createRepository(RepositoryType.GO_MODULES, "proxy.golang.org", "https://proxy.golang.org", true, false, false, null, null, null);
233+
qm.createRepository(RepositoryType.GITHUB, "github.com", "https://github.com", true, false, false, null, null, null);
234234
}
235235
}
236236

src/main/java/org/dependencytrack/persistence/QueryManager.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,12 +1237,12 @@ public boolean repositoryExist(RepositoryType type, String identifier) {
12371237
return getRepositoryQueryManager().repositoryExist(type, identifier);
12381238
}
12391239

1240-
public Repository createRepository(RepositoryType type, String identifier, String url, boolean enabled, boolean internal, boolean isAuthenticationRequired, String username, String password) {
1241-
return getRepositoryQueryManager().createRepository(type, identifier, url, enabled, internal, isAuthenticationRequired, username, password);
1240+
public Repository createRepository(RepositoryType type, String identifier, String url, boolean enabled, boolean internal, boolean isAuthenticationRequired, String username, String password, String bearerToken) {
1241+
return getRepositoryQueryManager().createRepository(type, identifier, url, enabled, internal, isAuthenticationRequired, username, password, bearerToken);
12421242
}
12431243

1244-
public Repository updateRepository(UUID uuid, String identifier, String url, boolean internal, boolean authenticationRequired, String username, String password, boolean enabled) {
1245-
return getRepositoryQueryManager().updateRepository(uuid, identifier, url, internal, authenticationRequired, username, password, enabled);
1244+
public Repository updateRepository(UUID uuid, String identifier, String url, boolean internal, boolean authenticationRequired, String username, String password, String bearerToken, boolean enabled) {
1245+
return getRepositoryQueryManager().updateRepository(uuid, identifier, url, internal, authenticationRequired, username, password, bearerToken, enabled);
12461246
}
12471247

12481248
public RepositoryMetaComponent getRepositoryMetaComponent(RepositoryType repositoryType, String namespace, String name) {

src/main/java/org/dependencytrack/persistence/RepositoryQueryManager.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,10 @@ public boolean repositoryExist(RepositoryType type, String identifier) {
143143
* @param isAuthenticationRequired if the repository needs authentication or not
144144
* @param username the username to access the (authenticated) repository with
145145
* @param password the password to access the (authenticated) repository with
146+
* @param bearerToken the token to access the (authenticated) repository with
146147
* @return the created Repository
147148
*/
148-
public Repository createRepository(RepositoryType type, String identifier, String url, boolean enabled, boolean internal, boolean isAuthenticationRequired, String username, String password) {
149+
public Repository createRepository(RepositoryType type, String identifier, String url, boolean enabled, boolean internal, boolean isAuthenticationRequired, String username, String password, String bearerToken) {
149150
if (repositoryExist(type, identifier)) {
150151
return null;
151152
}
@@ -166,15 +167,21 @@ public Repository createRepository(RepositoryType type, String identifier, Strin
166167
repo.setEnabled(enabled);
167168
repo.setInternal(internal);
168169
repo.setAuthenticationRequired(isAuthenticationRequired);
169-
if (Boolean.TRUE.equals(isAuthenticationRequired) && (username != null || password != null)) {
170+
if (Boolean.TRUE.equals(isAuthenticationRequired) && (username != null || password != null || bearerToken != null)) {
170171
repo.setUsername(StringUtils.trimToNull(username));
172+
String msg = "password";
171173
try {
172174
if (password != null) {
173175
repo.setPassword(DataEncryption.encryptAsString(password));
174176
}
177+
msg = "bearerToken";
178+
if (bearerToken != null) {
179+
repo.setBearerToken(DataEncryption.encryptAsString(bearerToken));
180+
}
175181
} catch (Exception e) {
176-
LOGGER.error("An error occurred while saving password in encrypted state");
182+
LOGGER.error("An error occurred while saving %s in encrypted state".formatted(msg));
177183
}
184+
178185
}
179186
return persist(repo);
180187
}
@@ -189,10 +196,11 @@ public Repository createRepository(RepositoryType type, String identifier, Strin
189196
* @param authenticationRequired if the repository needs authentication or not
190197
* @param username the username to access the (authenticated) repository with
191198
* @param password the password to access the (authenticated) repository with
199+
* @param bearerToken the bearer token to access the (authenticated) repository with
192200
* @param enabled specifies if the repository is enabled
193201
* @return the updated Repository
194202
*/
195-
public Repository updateRepository(UUID uuid, String identifier, String url, boolean internal, boolean authenticationRequired, String username, String password, boolean enabled) {
203+
public Repository updateRepository(UUID uuid, String identifier, String url, boolean internal, boolean authenticationRequired, String username, String password, String bearerToken, boolean enabled) {
196204
final Repository repository = getObjectByUuid(Repository.class, uuid);
197205
repository.setIdentifier(identifier);
198206
repository.setUrl(url);
@@ -201,9 +209,11 @@ public Repository updateRepository(UUID uuid, String identifier, String url, boo
201209
if (!authenticationRequired) {
202210
repository.setUsername(null);
203211
repository.setPassword(null);
212+
repository.setBearerToken(null);
204213
} else {
205214
repository.setUsername(username);
206215
repository.setPassword(password);
216+
repository.setBearerToken(bearerToken);
207217
}
208218

209219
repository.setEnabled(enabled);

src/main/java/org/dependencytrack/resources/v1/RepositoryResource.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ public Response createRepository(Repository jsonRepository) {
198198
jsonRepository.isEnabled(),
199199
jsonRepository.isInternal(),
200200
jsonRepository.isAuthenticationRequired(),
201-
jsonRepository.getUsername(), jsonRepository.getPassword());
201+
jsonRepository.getUsername(), jsonRepository.getPassword(),
202+
jsonRepository.getBearerToken());
202203

203204
return Response.status(Response.Status.CREATED).entity(repository).build();
204205
} else {
@@ -234,17 +235,24 @@ public Response updateRepository(Repository jsonRepository) {
234235
Repository repository = qm.getObjectByUuid(Repository.class, jsonRepository.getUuid());
235236
if (repository != null) {
236237
final String url = StringUtils.trimToNull(jsonRepository.getUrl());
238+
String msg = "password";
237239
try {
238240
// The password is not passed to the front-end, so it should only be overwritten if it is not null or not set to default value coming from ui
239241
final String updatedPassword = jsonRepository.getPassword()!=null && !jsonRepository.getPassword().equals(ENCRYPTED_PLACEHOLDER)
240242
? DataEncryption.encryptAsString(jsonRepository.getPassword())
241243
: repository.getPassword();
242244

245+
// The bearerToken is not passed to the front-end, so it should only be overwritten if it is not null or not set to default value coming from ui
246+
msg = "bearerToken";
247+
final String updatedBearerToken = jsonRepository.getBearerToken()!=null && !jsonRepository.getBearerToken().equals(ENCRYPTED_PLACEHOLDER)
248+
? DataEncryption.encryptAsString(jsonRepository.getBearerToken())
249+
: repository.getBearerToken();
250+
243251
repository = qm.updateRepository(jsonRepository.getUuid(), repository.getIdentifier(), url,
244-
jsonRepository.isInternal(), jsonRepository.isAuthenticationRequired(), jsonRepository.getUsername(), updatedPassword, jsonRepository.isEnabled());
252+
jsonRepository.isInternal(), jsonRepository.isAuthenticationRequired(), jsonRepository.getUsername(), updatedPassword, updatedBearerToken, jsonRepository.isEnabled());
245253
return Response.ok(repository).build();
246254
} catch (Exception e) {
247-
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("The specified repository password could not be encrypted.").build();
255+
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("The specified repository %s could not be encrypted.".formatted(msg)).build();
248256
}
249257
} else {
250258
return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the repository could not be found.").build();

src/main/java/org/dependencytrack/tasks/repositories/AbstractMetaAnalyzer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public abstract class AbstractMetaAnalyzer implements IMetaAnalyzer {
5252

5353
protected String password;
5454

55+
protected String bearerToken;
56+
5557
/**
5658
* {@inheritDoc}
5759
*/
@@ -66,9 +68,10 @@ public void setRepositoryBaseUrl(String baseUrl) {
6668
this.baseUrl = baseUrl;
6769
}
6870

69-
public void setRepositoryUsernameAndPassword(String username, String password) {
71+
public void setCredentials(String username, String password, String bearerToken) {
7072
this.username = StringUtils.trimToNull(username);
7173
this.password = StringUtils.trimToNull(password);
74+
this.bearerToken = StringUtils.trimToNull(bearerToken);
7275
}
7376

7477
protected String urlEncode(final String value) {
@@ -105,8 +108,8 @@ protected CloseableHttpResponse processHttpRequest(String url) throws IOExceptio
105108
URIBuilder uriBuilder = new URIBuilder(url);
106109
final HttpUriRequest request = new HttpGet(uriBuilder.build().toString());
107110
request.addHeader("accept", "application/json");
108-
if (username != null || password != null) {
109-
request.addHeader("Authorization", HttpUtil.basicAuthHeaderValue(username, password));
111+
if (username != null || password != null || bearerToken != null) {
112+
request.addHeader("Authorization", HttpUtil.constructAuthHeaderValue(username, password, bearerToken));
110113
}
111114
return HttpClientPool.getClient().execute(request);
112115
}catch (URISyntaxException ex){

src/main/java/org/dependencytrack/tasks/repositories/GithubMetaAnalyzer.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,6 @@ public void setRepositoryBaseUrl(String baseUrl) {
6666
this.repositoryUrl = baseUrl;
6767
}
6868

69-
/**
70-
* {@inheritDoc}
71-
*/
72-
@Override
73-
public void setRepositoryUsernameAndPassword(String username, String password) {
74-
this.repositoryUser = username;
75-
this.repositoryPassword = password;
76-
}
77-
7869
/**
7970
* {@inheritDoc}
8071
*/

src/main/java/org/dependencytrack/tasks/repositories/IMetaAnalyzer.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,13 @@ public interface IMetaAnalyzer {
4141

4242
/**
4343
* Sets the username and password (or access token) to use for authentication with the repository. Should not be used for repositories that do not
44-
* use Basic authentication.
44+
* use Basic or Bearer authentication.
4545
* @param username the username for access to the repository.
4646
* @param password the password or access token to be used for the repository.
47+
* @param bearerToken the password or access token to be used for the repository.
4748
* @since 4.6.0
4849
*/
49-
void setRepositoryUsernameAndPassword(String username, String password);
50+
void setCredentials(String username, String password, String bearerToken);
5051

5152
/**
5253
* Returns the type of repositry the analyzer supports.
@@ -154,7 +155,7 @@ public void setRepositoryBaseUrl(String baseUrl) {
154155
}
155156

156157
@Override
157-
public void setRepositoryUsernameAndPassword(String username, String password) {
158+
public void setCredentials(String username, String password, String bearerToken) {
158159

159160
}
160161

src/main/java/org/dependencytrack/tasks/repositories/RepositoryMetaAnalyzerTask.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,16 @@ private void analyze(final QueryManager qm, final Component component, final IMe
180180

181181
if (Boolean.TRUE.equals(repository.isAuthenticationRequired())) {
182182
try {
183+
LOGGER.error("decrypting credentials");
183184
String decryptedPassword = null;
185+
String decryptedBearerToken = null;
186+
if (repository.getBearerToken() != null) {
187+
decryptedBearerToken = DebugDataEncryption.decryptAsString(repository.getBearerToken());
188+
}
184189
if (repository.getPassword() != null) {
185190
decryptedPassword = DebugDataEncryption.decryptAsString(repository.getPassword());
186191
}
187-
analyzer.setRepositoryUsernameAndPassword(repository.getUsername(), decryptedPassword);
192+
analyzer.setCredentials(repository.getUsername(), decryptedPassword, decryptedBearerToken);
188193
} catch (Exception e) {
189194
LOGGER.error("Failed decrypting password for repository: " + repository.getIdentifier(), e);
190195
}

src/main/java/org/dependencytrack/upgrade/UpgradeItems.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class UpgradeItems {
4141
UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4110.v4110Updater.class);
4242
UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4120.v4120Updater.class);
4343
UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4122.v4122Updater.class);
44+
UPGRADE_ITEMS.add(org.dependencytrack.upgrade.v4130.v4130Updater.class);
4445
}
4546

4647
static List<Class<? extends UpgradeItem>> getUpgradeItems() {

0 commit comments

Comments
 (0)