Skip to content

Commit 2d9a588

Browse files
authored
Add Project Access Tokens (#1018)
1 parent f9baa81 commit 2d9a588

File tree

7 files changed

+323
-2
lines changed

7 files changed

+323
-2
lines changed

Diff for: src/main/java/org/gitlab4j/api/Constants.java

+22
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,28 @@ public String toString() {
919919
}
920920
}
921921

922+
/** Enum to use for specifying the project token scope. */
923+
public enum ProjectAccessTokenScope {
924+
API, READ_API, READ_REGISTRY, WRITE_REGISTRY, READ_REPOSITORY, WRITE_REPOSITORY, CREATE_RUNNER;
925+
926+
private static JacksonJsonEnumHelper<ProjectAccessTokenScope> enumHelper = new JacksonJsonEnumHelper<>(ProjectAccessTokenScope.class);
927+
928+
@JsonCreator
929+
public static ProjectAccessTokenScope forValue(String value) {
930+
return enumHelper.forValue(value);
931+
}
932+
933+
@JsonValue
934+
public String toValue() {
935+
return (enumHelper.toString(this));
936+
}
937+
938+
@Override
939+
public String toString() {
940+
return (enumHelper.toString(this));
941+
}
942+
}
943+
922944
/** Enum for the build_git_strategy of the project instance. */
923945
enum SquashOption {
924946

Diff for: src/main/java/org/gitlab4j/api/ProjectApi.java

+98
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.gitlab4j.api.models.Member;
5252
import org.gitlab4j.api.models.Namespace;
5353
import org.gitlab4j.api.models.Project;
54+
import org.gitlab4j.api.models.ProjectAccessToken;
5455
import org.gitlab4j.api.models.ProjectApprovalsConfig;
5556
import org.gitlab4j.api.models.ProjectFetches;
5657
import org.gitlab4j.api.models.ProjectFilter;
@@ -3901,4 +3902,101 @@ public RemoteMirror updateRemoteMirror(Object projectIdOrPath, Long mirrorId, Bo
39013902
"projects", getProjectIdOrPath(projectIdOrPath), "remote_mirrors", mirrorId);
39023903
return (response.readEntity(RemoteMirror.class));
39033904
}
3905+
3906+
/**
3907+
* Lists the projects access tokens for the project.
3908+
*
3909+
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
3910+
* @return the list of ProjectAccessTokens. The token and lastUsedAt attribute of each object is unset.
3911+
* @throws GitLabApiException if any exception occurs
3912+
*/
3913+
public List<ProjectAccessToken> listProjectAccessTokens(Object projectIdOrPath) throws GitLabApiException {
3914+
Response response = get(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
3915+
return (response.readEntity(new GenericType<List<ProjectAccessToken>>() { }));
3916+
}
3917+
3918+
/**
3919+
* Gets the specific project access token.
3920+
* Only working with GitLab 14.10 and above.
3921+
*
3922+
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
3923+
* @param tokenId the id of the token
3924+
* @return the ProjectAccessToken. The token attribute of the object is unset.
3925+
* @throws GitLabApiException if any exception occurs
3926+
*/
3927+
public ProjectAccessToken getProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
3928+
Response response = get(Response.Status.OK, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId);
3929+
return (response.readEntity(ProjectAccessToken.class));
3930+
}
3931+
3932+
/**
3933+
* Creates a new project access token.
3934+
*
3935+
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
3936+
* @param name the name of the token
3937+
* @param scopes the scope of the token
3938+
* @param expiresAt the date when the token should expire
3939+
* @param accessLevel The access level of the token is optional. It can either be 10, 20, 30, 40, or 50.
3940+
* @return the newly created ProjectAccessToken. The lastUsedAt attribute of each object is unset.
3941+
* @throws GitLabApiException if any exception occurs
3942+
*/
3943+
public ProjectAccessToken createProjectAccessToken(Object projectIdOrPath, String name, List<Constants.ProjectAccessTokenScope> scopes, Date expiresAt, Long accessLevel) throws GitLabApiException {
3944+
GitLabApiForm formData = new GitLabApiForm()
3945+
.withParam("name", name, true)
3946+
.withParam("expires_at", expiresAt, true)
3947+
.withParam("scopes", scopes, true)
3948+
.withParam("access_level", accessLevel, false);
3949+
Response response = post(Response.Status.CREATED, formData,
3950+
"projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
3951+
return (response.readEntity(ProjectAccessToken.class));
3952+
}
3953+
3954+
/**
3955+
* Creates a new project access token.
3956+
* The default value for the accessLevel is used.
3957+
*
3958+
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
3959+
* @param name the name of the token
3960+
* @param scopes the scope of the token
3961+
* @param expiresAt the date when the token should expire
3962+
* @return the newly created ProjectAccessToken. The lastUsedAt attribute of each object is unset.
3963+
* @throws GitLabApiException if any exception occurs
3964+
*/
3965+
public ProjectAccessToken createProjectAccessToken(Object projectIdOrPath, String name, List<Constants.ProjectAccessTokenScope> scopes, Date expiresAt) throws GitLabApiException {
3966+
GitLabApiForm formData = new GitLabApiForm()
3967+
.withParam("name", name, true)
3968+
.withParam("expires_at", ISO8601.dateOnly(expiresAt), true)
3969+
.withParam("scopes", scopes, true)
3970+
.withParam("access_level", (Object) null, false);
3971+
Response response = post(Response.Status.CREATED, formData,
3972+
"projects", getProjectIdOrPath(projectIdOrPath), "access_tokens");
3973+
return (response.readEntity(ProjectAccessToken.class));
3974+
}
3975+
3976+
/**
3977+
* Rotates the given project access token.
3978+
* The token is revoked and a new one which will expire in one week is created to replace it.
3979+
* Only working with GitLab 16.0 and above.
3980+
*
3981+
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
3982+
* @param tokenId the id
3983+
* @return the newly created ProjectAccessToken.
3984+
* @throws GitLabApiException if any exception occurs
3985+
*/
3986+
public ProjectAccessToken rotateProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
3987+
Response response = post(Response.Status.OK, (Object) null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId, "rotate");
3988+
return (response.readEntity(ProjectAccessToken.class));
3989+
}
3990+
3991+
/**
3992+
* Revokes the project access token.
3993+
*
3994+
* @param projectIdOrPath the project in the form of a Long(ID), String(path), or Project instance
3995+
* @param tokenId the id of the token, which should be revoked
3996+
* @throws GitLabApiException if any exception occurs
3997+
*/
3998+
public void revokeProjectAccessToken(Object projectIdOrPath, Long tokenId) throws GitLabApiException {
3999+
delete(Response.Status.NO_CONTENT, null, "projects", getProjectIdOrPath(projectIdOrPath), "access_tokens", tokenId);
4000+
}
4001+
39044002
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package org.gitlab4j.api.models;
2+
3+
import com.fasterxml.jackson.annotation.JsonFormat;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import org.gitlab4j.api.Constants;
6+
import org.gitlab4j.api.utils.JacksonJson;
7+
8+
import java.util.Date;
9+
import java.util.List;
10+
11+
public class ProjectAccessToken {
12+
private Long userId;
13+
private List<Constants.ProjectAccessTokenScope> scopes;
14+
private String name;
15+
private Date expiresAt;
16+
private Long id;
17+
private Boolean active;
18+
private Date createdAt;
19+
private Boolean revoked;
20+
private Long accessLevel;
21+
private Date lastUsedAt;
22+
private String token;
23+
24+
public Long getUserId() {
25+
return userId;
26+
}
27+
28+
public void setUserId(Long userId) {
29+
this.userId = userId;
30+
}
31+
32+
public List<Constants.ProjectAccessTokenScope> getScopes() {
33+
return scopes;
34+
}
35+
36+
public void setScopes(List<Constants.ProjectAccessTokenScope> scopes) {
37+
this.scopes = scopes;
38+
}
39+
40+
public String getName() {
41+
return name;
42+
}
43+
44+
public void setName(String name) {
45+
this.name = name;
46+
}
47+
48+
public Date getExpiresAt() {
49+
return expiresAt;
50+
}
51+
52+
public void setExpiresAt(Date expiredAt) {
53+
this.expiresAt = expiredAt;
54+
}
55+
56+
public Long getId() {
57+
return id;
58+
}
59+
60+
public void setId(Long id) {
61+
this.id = id;
62+
}
63+
64+
public Boolean isActive() {
65+
return active;
66+
}
67+
68+
public void setActive(Boolean active) {
69+
this.active = active;
70+
}
71+
72+
public Date getCreatedAt() {
73+
return createdAt;
74+
}
75+
76+
public void setCreatedAt(Date createdAt) {
77+
this.createdAt = createdAt;
78+
}
79+
80+
public Boolean isRevoked() {
81+
return revoked;
82+
}
83+
84+
public void setRevoked(Boolean revoked) {
85+
this.revoked = revoked;
86+
}
87+
88+
public Long getAccessLevel() {
89+
return accessLevel;
90+
}
91+
92+
public void setAccessLevel(Long accessLevel) {
93+
this.accessLevel = accessLevel;
94+
}
95+
96+
public Date getLastUsedAt() {
97+
return lastUsedAt;
98+
}
99+
100+
public void setLastUsedAt(Date lastUsedAt) {
101+
this.lastUsedAt = lastUsedAt;
102+
}
103+
104+
public String getToken() {
105+
return token;
106+
}
107+
108+
public void setToken(String token) {
109+
this.token = token;
110+
}
111+
112+
@Override
113+
public String toString() {
114+
return JacksonJson.toJsonString(this);
115+
}
116+
}

Diff for: src/test/java/org/gitlab4j/api/SetupIntegrationTestExtension.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ private static void seedData() throws GitLabApiException {
168168

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

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

188-
// Create the sudo as user if it does not exists
188+
// Create the sudo as user if it does not exist
189189
username = HelperUtils.getProperty(SUDO_AS_USERNAME_KEY, "user1");
190190
optionalUser = gitLabApi.getUserApi().getOptionalUser(username);
191191
if (!optionalUser.isPresent()) {

Diff for: src/test/java/org/gitlab4j/api/TestGitLabApiBeans.java

+7
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
import org.gitlab4j.api.models.Pipeline;
9797
import org.gitlab4j.api.models.PipelineSchedule;
9898
import org.gitlab4j.api.models.Project;
99+
import org.gitlab4j.api.models.ProjectAccessToken;
99100
import org.gitlab4j.api.models.ProjectApprovalsConfig;
100101
import org.gitlab4j.api.models.ProjectFetches;
101102
import org.gitlab4j.api.models.ProjectGroup;
@@ -783,4 +784,10 @@ public void testSearchBlobs() throws Exception {
783784
List<SearchBlob> searchResults = unmarshalResourceList(SearchBlob.class, "wiki-blobs.json");
784785
assertTrue(compareJson(searchResults, "wiki-blobs.json"));
785786
}
787+
788+
@Test
789+
public void testProjectAccessToken() throws Exception {
790+
ProjectAccessToken token = unmarshalResource(ProjectAccessToken.class, "project-access-token.json");
791+
assertTrue(compareJson(token, "project-access-token.json"));
792+
}
786793
}

Diff for: src/test/java/org/gitlab4j/api/TestProjectApi.java

+63
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@
2525

2626
import static org.junit.jupiter.api.Assertions.assertEquals;
2727
import static org.junit.jupiter.api.Assertions.assertFalse;
28+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
2829
import static org.junit.jupiter.api.Assertions.assertNotNull;
2930
import static org.junit.jupiter.api.Assertions.assertNull;
3031
import static org.junit.jupiter.api.Assertions.assertTrue;
3132
import static org.junit.jupiter.api.Assumptions.assumeTrue;
3233

34+
import java.time.Instant;
3335
import java.util.Arrays;
36+
import java.util.Date;
3437
import java.util.List;
3538
import java.util.Map;
3639
import java.util.Optional;
@@ -43,11 +46,13 @@
4346
import org.gitlab4j.api.models.Group;
4447
import org.gitlab4j.api.models.Member;
4548
import org.gitlab4j.api.models.Project;
49+
import org.gitlab4j.api.models.ProjectAccessToken;
4650
import org.gitlab4j.api.models.ProjectFetches;
4751
import org.gitlab4j.api.models.ProjectFilter;
4852
import org.gitlab4j.api.models.User;
4953
import org.gitlab4j.api.models.Variable;
5054
import org.gitlab4j.api.models.Visibility;
55+
import org.gitlab4j.api.utils.ISO8601;
5156
import org.junit.jupiter.api.AfterAll;
5257
import org.junit.jupiter.api.BeforeAll;
5358
import org.junit.jupiter.api.BeforeEach;
@@ -884,4 +889,62 @@ public void testTriggerHousekeeping() throws GitLabApiException {
884889
}
885890
}
886891
}
892+
893+
@Test
894+
public void testCreateProjectAccessToken() throws GitLabApiException {
895+
final String tokenName = "token-" + HelperUtils.getRandomInt(1000);;
896+
final List<Constants.ProjectAccessTokenScope> scopes = Arrays.asList(Constants.ProjectAccessTokenScope.READ_API, Constants.ProjectAccessTokenScope.READ_REPOSITORY);
897+
final Date expiresAt = Date.from(Instant.now().plusSeconds(48*60*60));
898+
assertNotNull(testProject);
899+
900+
// This does not work with the GitLab version used for the integration tests
901+
// final int size = gitLabApi.getProjectApi().listProjectAccessTokens(testProject.getId()).size() + 1;
902+
//
903+
// ProjectAccessToken token = gitLabApi.getProjectApi().createProjectAccessToken(testProject.getId(), tokenName, scopes, expiresAt);
904+
//
905+
// assertEquals(size, gitLabApi.getProjectApi().listProjectAccessTokens(testProject.getId()).size());
906+
// assertNotNull(token.getCreatedAt());
907+
// assertEquals(ISO8601.dateOnly(expiresAt), ISO8601.dateOnly(token.getExpiresAt()));
908+
// assertNotNull(token.getId());
909+
// assertEquals(tokenName, token.getName());
910+
// assertFalse(token.isRevoked());
911+
// assertEquals(scopes, token.getScopes());
912+
// assertNotNull(token.getToken());
913+
// assertNotEquals(token.getToken(), "");
914+
// assertNotNull(token.getUserId());
915+
// // unset
916+
// assertNull(token.getLastUsedAt());
917+
//
918+
// gitLabApi.getProjectApi().revokeProjectAccessToken(testProject.getId(), token.getId());
919+
// assertTrue(gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), token.getId()).isRevoked());
920+
}
921+
922+
@Test
923+
public void testRotateProjectAccessToken() throws GitLabApiException {
924+
final String tokenName = "token-" + HelperUtils.getRandomInt(1000);;
925+
final List<Constants.ProjectAccessTokenScope> scopes = Arrays.asList(Constants.ProjectAccessTokenScope.READ_API, Constants.ProjectAccessTokenScope.READ_REPOSITORY);
926+
final Date expiresAt = Date.from(Instant.now().plusSeconds(7*24*60*60));
927+
assertNotNull(testProject);
928+
929+
// This does not work with the GitLab version used for the integration tests
930+
// ProjectAccessToken rotatedToken = gitLabApi.getProjectApi().createProjectAccessToken(testProject.getId(), tokenName, scopes, expiresAt);
931+
// ProjectAccessToken token = gitLabApi.getProjectApi().rotateProjectAccessToken(testProject.getId(), rotatedToken.getId());
932+
// rotatedToken = gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), rotatedToken.getId());
933+
//
934+
// assertNotNull(token.getCreatedAt());
935+
// assertEquals(ISO8601.dateOnly(expiresAt), ISO8601.dateOnly(token.getExpiresAt()));
936+
// assertNotNull(token.getId());
937+
// assertNotEquals(rotatedToken.getId(), token.getId());
938+
// assertEquals(tokenName, token.getName());
939+
// assertFalse(token.isRevoked());
940+
// assertTrue(rotatedToken.isRevoked());
941+
// assertEquals(scopes, token.getScopes());
942+
// assertNotNull(token.getToken());
943+
// assertNotEquals(token.getToken(), "");
944+
// assertNotEquals(rotatedToken.getToken(), token.getToken());
945+
// assertNotNull(token.getUserId());
946+
//
947+
// gitLabApi.getProjectApi().revokeProjectAccessToken(testProject.getId(), token.getId());
948+
// assertTrue(gitLabApi.getProjectApi().getProjectAccessToken(testProject.getId(), token.getId()).isRevoked());
949+
}
887950
}

0 commit comments

Comments
 (0)