Skip to content

Commit

Permalink
Add Project Access Tokens (#1018)
Browse files Browse the repository at this point in the history
  • Loading branch information
bensofficial authored Aug 23, 2023
1 parent f9baa81 commit 2d9a588
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 2 deletions.
22 changes: 22 additions & 0 deletions src/main/java/org/gitlab4j/api/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,28 @@ public String toString() {
}
}

/** Enum to use for specifying the project token scope. */
public enum ProjectAccessTokenScope {
API, READ_API, READ_REGISTRY, WRITE_REGISTRY, READ_REPOSITORY, WRITE_REPOSITORY, CREATE_RUNNER;

private static JacksonJsonEnumHelper<ProjectAccessTokenScope> enumHelper = new JacksonJsonEnumHelper<>(ProjectAccessTokenScope.class);

@JsonCreator
public static ProjectAccessTokenScope forValue(String value) {
return enumHelper.forValue(value);
}

@JsonValue
public String toValue() {
return (enumHelper.toString(this));
}

@Override
public String toString() {
return (enumHelper.toString(this));
}
}

/** Enum for the build_git_strategy of the project instance. */
enum SquashOption {

Expand Down
98 changes: 98 additions & 0 deletions src/main/java/org/gitlab4j/api/ProjectApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.gitlab4j.api.models.Member;
import org.gitlab4j.api.models.Namespace;
import org.gitlab4j.api.models.Project;
import org.gitlab4j.api.models.ProjectAccessToken;
import org.gitlab4j.api.models.ProjectApprovalsConfig;
import org.gitlab4j.api.models.ProjectFetches;
import org.gitlab4j.api.models.ProjectFilter;
Expand Down Expand Up @@ -3901,4 +3902,101 @@ public RemoteMirror updateRemoteMirror(Object projectIdOrPath, Long mirrorId, Bo
"projects", getProjectIdOrPath(projectIdOrPath), "remote_mirrors", mirrorId);
return (response.readEntity(RemoteMirror.class));
}

/**
* Lists the projects access tokens for the project.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @return the list of ProjectAccessTokens. The token and lastUsedAt attribute of each object is unset.
* @throws GitLabApiException if any exception occurs
*/
public List<ProjectAccessToken> listProjectAccessTokens(Object projectIdOrPath) throws GitLabApiException {
Response response = get(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
return (response.readEntity(new GenericType<List<ProjectAccessToken>>() { }));
}

/**
* Gets the specific project access token.
* Only working with GitLab 14.10 and above.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param tokenId the id of the token
* @return the ProjectAccessToken. The token attribute of the object is unset.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken getProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
Response response = get(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId);
return (response.readEntity(ProjectAccessToken.class));
}

/**
* Creates a new project access token.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param name the name of the token
* @param scopes the scope of the token
* @param expiresAt the date when the token should expire
* @param accessLevel The access level of the token is optional. It can either be 10, 20, 30, 40, or 50.
* @return the newly created ProjectAccessToken. The lastUsedAt attribute of each object is unset.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken createProjectAccessToken(Object projectIdOrPath, String name, List<Constants.ProjectAccessTokenScope> scopes, Date expiresAt, Long accessLevel) throws GitLabApiException {
GitLabApiForm formData = new GitLabApiForm()
.withParam("name", name, true)
.withParam("expires_at", expiresAt, true)
.withParam("scopes", scopes, true)
.withParam("access_level", accessLevel, false);
Response response = post(Response.Status.CREATED, formData,
"projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
return (response.readEntity(ProjectAccessToken.class));
}

/**
* Creates a new project access token.
* The default value for the accessLevel is used.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param name the name of the token
* @param scopes the scope of the token
* @param expiresAt the date when the token should expire
* @return the newly created ProjectAccessToken. The lastUsedAt attribute of each object is unset.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken createProjectAccessToken(Object projectIdOrPath, String name, List<Constants.ProjectAccessTokenScope> scopes, Date expiresAt) throws GitLabApiException {
GitLabApiForm formData = new GitLabApiForm()
.withParam("name", name, true)
.withParam("expires_at", ISO8601.dateOnly(expiresAt), true)
.withParam("scopes", scopes, true)
.withParam("access_level", (Object) null, false);
Response response = post(Response.Status.CREATED, formData,
"projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
return (response.readEntity(ProjectAccessToken.class));
}

/**
* Rotates the given project access token.
* The token is revoked and a new one which will expire in one week is created to replace it.
* Only working with GitLab 16.0 and above.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param tokenId the id
* @return the newly created ProjectAccessToken.
* @throws GitLabApiException if any exception occurs
*/
public ProjectAccessToken rotateProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
Response response = post(Response.Status.OK, (Object) null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId, "rotate");
return (response.readEntity(ProjectAccessToken.class));
}

/**
* Revokes the project access token.
*
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
* @param tokenId the id of the token, which should be revoked
* @throws GitLabApiException if any exception occurs
*/
public void revokeProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
delete(Response.Status.NO_CONTENT, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId);
}

}
116 changes: 116 additions & 0 deletions src/main/java/org/gitlab4j/api/models/ProjectAccessToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.gitlab4j.api.models;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.gitlab4j.api.Constants;
import org.gitlab4j.api.utils.JacksonJson;

import java.util.Date;
import java.util.List;

public class ProjectAccessToken {
private Long userId;
private List<Constants.ProjectAccessTokenScope> scopes;
private String name;
private Date expiresAt;
private Long id;
private Boolean active;
private Date createdAt;
private Boolean revoked;
private Long accessLevel;
private Date lastUsedAt;
private String token;

public Long getUserId() {
return userId;
}

public void setUserId(Long userId) {
this.userId = userId;
}

public List<Constants.ProjectAccessTokenScope> getScopes() {
return scopes;
}

public void setScopes(List<Constants.ProjectAccessTokenScope> scopes) {
this.scopes = scopes;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Date getExpiresAt() {
return expiresAt;
}

public void setExpiresAt(Date expiredAt) {
this.expiresAt = expiredAt;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Boolean isActive() {
return active;
}

public void setActive(Boolean active) {
this.active = active;
}

public Date getCreatedAt() {
return createdAt;
}

public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}

public Boolean isRevoked() {
return revoked;
}

public void setRevoked(Boolean revoked) {
this.revoked = revoked;
}

public Long getAccessLevel() {
return accessLevel;
}

public void setAccessLevel(Long accessLevel) {
this.accessLevel = accessLevel;
}

public Date getLastUsedAt() {
return lastUsedAt;
}

public void setLastUsedAt(Date lastUsedAt) {
this.lastUsedAt = lastUsedAt;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}

@Override
public String toString() {
return JacksonJson.toJsonString(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ private static void seedData() throws GitLabApiException {

GitLabApi gitLabApi = GitLabApi.oauth2Login(TEST_HOST_URL, username, password, null, null, true);

// If the tester user doen't exists, create it
// If the tester user doesn't exist, create it
Optional<User> optionalUser = gitLabApi.getUserApi().getOptionalUser(TEST_LOGIN_USERNAME);
if (!optionalUser.isPresent()) {
User userSettings = new User()
Expand All @@ -185,7 +185,7 @@ private static void seedData() throws GitLabApiException {
// so use OAUTH2 to get the GitLabApi instance
gitLabApi = GitLabApi.oauth2Login(TEST_HOST_URL, TEST_LOGIN_USERNAME, TEST_LOGIN_PASSWORD, null, null, true);

// Create the sudo as user if it does not exists
// Create the sudo as user if it does not exist
username = HelperUtils.getProperty(SUDO_AS_USERNAME_KEY, "user1");
optionalUser = gitLabApi.getUserApi().getOptionalUser(username);
if (!optionalUser.isPresent()) {
Expand Down
7 changes: 7 additions & 0 deletions src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
import org.gitlab4j.api.models.Pipeline;
import org.gitlab4j.api.models.PipelineSchedule;
import org.gitlab4j.api.models.Project;
import org.gitlab4j.api.models.ProjectAccessToken;
import org.gitlab4j.api.models.ProjectApprovalsConfig;
import org.gitlab4j.api.models.ProjectFetches;
import org.gitlab4j.api.models.ProjectGroup;
Expand Down Expand Up @@ -783,4 +784,10 @@ public void testSearchBlobs() throws Exception {
List<SearchBlob> searchResults = unmarshalResourceList(SearchBlob.class, "wiki-blobs.json");
assertTrue(compareJson(searchResults, "wiki-blobs.json"));
}

@Test
public void testProjectAccessToken() throws Exception {
ProjectAccessToken token = unmarshalResource(ProjectAccessToken.class, "project-access-token.json");
assertTrue(compareJson(token, "project-access-token.json"));
}
}
63 changes: 63 additions & 0 deletions src/test/java/org/gitlab4j/api/TestProjectApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -43,11 +46,13 @@
import org.gitlab4j.api.models.Group;
import org.gitlab4j.api.models.Member;
import org.gitlab4j.api.models.Project;
import org.gitlab4j.api.models.ProjectAccessToken;
import org.gitlab4j.api.models.ProjectFetches;
import org.gitlab4j.api.models.ProjectFilter;
import org.gitlab4j.api.models.User;
import org.gitlab4j.api.models.Variable;
import org.gitlab4j.api.models.Visibility;
import org.gitlab4j.api.utils.ISO8601;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -884,4 +889,62 @@ public void testTriggerHousekeeping() throws GitLabApiException {
}
}
}

@Test
public void testCreateProjectAccessToken() throws GitLabApiException {
final String tokenName = "token-" + HelperUtils.getRandomInt(1000);;
final List<Constants.ProjectAccessTokenScope> scopes = Arrays.asList(Constants.ProjectAccessTokenScope.READ_API, Constants.ProjectAccessTokenScope.READ_REPOSITORY);
final Date expiresAt = Date.from(Instant.now().plusSeconds(48*60*60));
assertNotNull(testProject);

// This does not work with the GitLab version used for the integration tests
// final int size = gitLabApi.getProjectApi().listProjectAccessTokens(testProject.getId()).size() + 1;
//
// ProjectAccessToken token = gitLabApi.getProjectApi().createProjectAccessToken(testProject.getId(), tokenName, scopes, expiresAt);
//
// assertEquals(size, gitLabApi.getProjectApi().listProjectAccessTokens(testProject.getId()).size());
// assertNotNull(token.getCreatedAt());
// assertEquals(ISO8601.dateOnly(expiresAt), ISO8601.dateOnly(token.getExpiresAt()));
// assertNotNull(token.getId());
// assertEquals(tokenName, token.getName());
// assertFalse(token.isRevoked());
// assertEquals(scopes, token.getScopes());
// assertNotNull(token.getToken());
// assertNotEquals(token.getToken(), "");
// assertNotNull(token.getUserId());
// // unset
// assertNull(token.getLastUsedAt());
//
// gitLabApi.getProjectApi().revokeProjectAccessToken(testProject.getId(), token.getId());
// assertTrue(gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), token.getId()).isRevoked());
}

@Test
public void testRotateProjectAccessToken() throws GitLabApiException {
final String tokenName = "token-" + HelperUtils.getRandomInt(1000);;
final List<Constants.ProjectAccessTokenScope> scopes = Arrays.asList(Constants.ProjectAccessTokenScope.READ_API, Constants.ProjectAccessTokenScope.READ_REPOSITORY);
final Date expiresAt = Date.from(Instant.now().plusSeconds(7*24*60*60));
assertNotNull(testProject);

// This does not work with the GitLab version used for the integration tests
// ProjectAccessToken rotatedToken = gitLabApi.getProjectApi().createProjectAccessToken(testProject.getId(), tokenName, scopes, expiresAt);
// ProjectAccessToken token = gitLabApi.getProjectApi().rotateProjectAccessToken(testProject.getId(), rotatedToken.getId());
// rotatedToken = gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), rotatedToken.getId());
//
// assertNotNull(token.getCreatedAt());
// assertEquals(ISO8601.dateOnly(expiresAt), ISO8601.dateOnly(token.getExpiresAt()));
// assertNotNull(token.getId());
// assertNotEquals(rotatedToken.getId(), token.getId());
// assertEquals(tokenName, token.getName());
// assertFalse(token.isRevoked());
// assertTrue(rotatedToken.isRevoked());
// assertEquals(scopes, token.getScopes());
// assertNotNull(token.getToken());
// assertNotEquals(token.getToken(), "");
// assertNotEquals(rotatedToken.getToken(), token.getToken());
// assertNotNull(token.getUserId());
//
// gitLabApi.getProjectApi().revokeProjectAccessToken(testProject.getId(), token.getId());
// assertTrue(gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), token.getId()).isRevoked());
}
}
Loading

0 comments on commit 2d9a588

Please sign in to comment.