diff --git a/examples/personal_access_tokens.go b/examples/personal_access_tokens.go index 5706ddabf..aa49b15ee 100644 --- a/examples/personal_access_tokens.go +++ b/examples/personal_access_tokens.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "log" + "time" "github.com/xanzy/go-gitlab" ) @@ -68,7 +69,11 @@ func patRotateExample() { log.Fatal(err) } - newPersonalAccessToken, _, err := git.PersonalAccessTokens.RotatePersonalAccessToken(12345) + expiry := gitlab.ISOTime(time.Date(2023, time.August, 15, 0, 0, 0, 0, time.UTC)) + opts := &gitlab.RotatePersonalAccessTokenOptions{ + ExpiresAt: &expiry, + } + newPersonalAccessToken, _, err := git.PersonalAccessTokens.RotatePersonalAccessToken(12345, opts) if err != nil { log.Fatal(err) } diff --git a/group_access_tokens.go b/group_access_tokens.go index 15f303a98..ccbe47b83 100644 --- a/group_access_tokens.go +++ b/group_access_tokens.go @@ -145,18 +145,27 @@ func (s *GroupAccessTokensService) CreateGroupAccessToken(gid interface{}, opt * return pat, resp, nil } +// RotateGroupAccessTokenOptions represents the available RotateGroupAccessToken() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/group_access_tokens.html#rotate-a-group-access-token +type RotateGroupAccessTokenOptions struct { + ExpiresAt *ISOTime `url:"expires_at,omitempty" json:"expires_at,omitempty"` +} + // RotateGroupAccessToken revokes a group access token and returns a new group -// access token that expires in one week. +// access token that expires in one week per default. // // GitLab API docs: // https://docs.gitlab.com/ee/api/group_access_tokens.html#rotate-a-group-access-token -func (s *GroupAccessTokensService) RotateGroupAccessToken(gid interface{}, id int, options ...RequestOptionFunc) (*GroupAccessToken, *Response, error) { +func (s *GroupAccessTokensService) RotateGroupAccessToken(gid interface{}, id int, opt *RotateGroupAccessTokenOptions, options ...RequestOptionFunc) (*GroupAccessToken, *Response, error) { groups, err := parseID(gid) if err != nil { return nil, nil, err } u := fmt.Sprintf("groups/%s/access_tokens/%d/rotate", PathEscape(groups), id) - req, err := s.client.NewRequest(http.MethodPost, u, nil, options) + req, err := s.client.NewRequest(http.MethodPost, u, opt, options) if err != nil { return nil, nil, err } diff --git a/group_access_tokens_test.go b/group_access_tokens_test.go index aa1d46501..93a358d87 100644 --- a/group_access_tokens_test.go +++ b/group_access_tokens_test.go @@ -146,6 +146,38 @@ func TestCreateGroupAccessToken(t *testing.T) { t.Errorf("GroupAccessTokens.CreateGroupAccessToken returned %+v, want %+v", groupAccessToken, want) } } +func TestRotateGroupAccessToken(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/groups/1/access_tokens/42/rotate", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + mustWriteHTTPResponse(t, w, "testdata/rotate_group_access_token.json") + }) + + createdAt, _ := time.Parse(time.RFC3339, "2023-08-01T15:00:00.00Z") + expiration := ISOTime(time.Date(2023, time.August, 15, 0, 0, 0, 0, time.UTC)) + opts := &RotateGroupAccessTokenOptions{ExpiresAt: &expiration} + rotatedToken, _, err := client.GroupAccessTokens.RotateGroupAccessToken(1, 42, opts) + if err != nil { + t.Errorf("GroupAccessTokens.RotateGroupAccessToken returned error: %v", err) + } + + want := &GroupAccessToken{ + ID: 42, + UserID: 1337, + Name: "Rotated Token", + Scopes: []string{"api"}, + ExpiresAt: &expiration, + CreatedAt: &createdAt, + Active: true, + Revoked: false, + Token: "s3cr3t", + AccessLevel: AccessLevelValue(30), + } + + if !reflect.DeepEqual(want, rotatedToken) { + t.Errorf("GroupAccessTokens.RotateGroupAccessToken returned %+v, want %+v", rotatedToken, want) + } +} func TestRevokeGroupAccessToken(t *testing.T) { mux, client := setup(t) diff --git a/personal_access_tokens.go b/personal_access_tokens.go index 63d294e6f..ba32bd8fd 100644 --- a/personal_access_tokens.go +++ b/personal_access_tokens.go @@ -120,14 +120,24 @@ func (s *PersonalAccessTokensService) GetSinglePersonalAccessToken(options ...Re return pat, resp, nil } +// RotatePersonalAccessTokenOptions represents the available RotatePersonalAccessToken() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/personal_access_tokens.html#rotate-a-personal-access-token +type RotatePersonalAccessTokenOptions struct { + ExpiresAt *ISOTime `url:"expires_at,omitempty" json:"expires_at,omitempty"` +} + // RotatePersonalAccessToken revokes a token and returns a new token that -// expires in one week. +// expires in one week per default. // // GitLab API docs: // https://docs.gitlab.com/ee/api/personal_access_tokens.html#rotate-a-personal-access-token -func (s *PersonalAccessTokensService) RotatePersonalAccessToken(token int, options ...RequestOptionFunc) (*PersonalAccessToken, *Response, error) { +func (s *PersonalAccessTokensService) RotatePersonalAccessToken(token int, opt *RotatePersonalAccessTokenOptions, options ...RequestOptionFunc) (*PersonalAccessToken, *Response, error) { u := fmt.Sprintf("personal_access_tokens/%d/rotate", token) - req, err := s.client.NewRequest(http.MethodPost, u, nil, options) + + req, err := s.client.NewRequest(http.MethodPost, u, opt, options) if err != nil { return nil, nil, err } diff --git a/personal_access_tokens_test.go b/personal_access_tokens_test.go index de92d830a..36eee51ea 100644 --- a/personal_access_tokens_test.go +++ b/personal_access_tokens_test.go @@ -225,6 +225,37 @@ func TestGetSinglePersonalAccessToken(t *testing.T) { } } +func TestRotatePersonalAccessToken(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/personal_access_tokens/42/rotate", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + mustWriteHTTPResponse(t, w, "testdata/rotate_personal_access_token.json") + }) + + createdAt, _ := time.Parse(time.RFC3339, "2023-08-01T15:00:00.000Z") + expiration := ISOTime(time.Date(2023, time.August, 15, 0, 0, 0, 0, time.UTC)) + opts := &RotatePersonalAccessTokenOptions{ExpiresAt: &expiration} + rotatedToken, _, err := client.PersonalAccessTokens.RotatePersonalAccessToken(42, opts) + if err != nil { + t.Errorf("PersonalAccessTokens.RotatePersonalAccessToken returned error: %v", err) + } + + want := &PersonalAccessToken{ + ID: 42, + UserID: 1337, + Name: "Rotated Token", + Scopes: []string{"api"}, + ExpiresAt: &expiration, + CreatedAt: &createdAt, + Active: true, + Revoked: false, + Token: "s3cr3t", + } + + if !reflect.DeepEqual(want, rotatedToken) { + t.Errorf("PersonalAccessTokens.RotatePersonalAccessTokens returned %+v, want %+v", rotatedToken, want) + } +} func TestRevokePersonalAccessToken(t *testing.T) { mux, client := setup(t) diff --git a/project_access_tokens.go b/project_access_tokens.go index 780e59a82..2d6057e05 100644 --- a/project_access_tokens.go +++ b/project_access_tokens.go @@ -146,18 +146,27 @@ func (s *ProjectAccessTokensService) CreateProjectAccessToken(pid interface{}, o return pat, resp, nil } +// RotateProjectAccessTokenOptions represents the available RotateProjectAccessToken() +// options. +// +// GitLab API docs: +// https://docs.gitlab.com/ee/api/project_access_tokens.html#rotate-a-project-access-token +type RotateProjectAccessTokenOptions struct { + ExpiresAt *ISOTime `url:"expires_at,omitempty" json:"expires_at,omitempty"` +} + // RotateProjectAccessToken revokes a project access token and returns a new -// project access token that expires in one week. +// project access token that expires in one week per default. // // GitLab API docs: // https://docs.gitlab.com/ee/api/project_access_tokens.html#rotate-a-project-access-token -func (s *ProjectAccessTokensService) RotateProjectAccessToken(pid interface{}, id int, options ...RequestOptionFunc) (*ProjectAccessToken, *Response, error) { +func (s *ProjectAccessTokensService) RotateProjectAccessToken(pid interface{}, id int, opt *RotateProjectAccessTokenOptions, options ...RequestOptionFunc) (*ProjectAccessToken, *Response, error) { projects, err := parseID(pid) if err != nil { return nil, nil, err } u := fmt.Sprintf("projects/%s/access_tokens/%d/rotate", PathEscape(projects), id) - req, err := s.client.NewRequest(http.MethodPost, u, nil, options) + req, err := s.client.NewRequest(http.MethodPost, u, opt, options) if err != nil { return nil, nil, err } diff --git a/project_access_tokens_test.go b/project_access_tokens_test.go index efe22304f..e0fc86130 100644 --- a/project_access_tokens_test.go +++ b/project_access_tokens_test.go @@ -147,6 +147,39 @@ func TestCreateProjectAccessToken(t *testing.T) { } } +func TestRotateProjectAccessToken(t *testing.T) { + mux, client := setup(t) + mux.HandleFunc("/api/v4/projects/1/access_tokens/42/rotate", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodPost) + mustWriteHTTPResponse(t, w, "testdata/rotate_project_access_token.json") + }) + + createdAt, _ := time.Parse(time.RFC3339, "2023-08-01T15:00:00.000Z") + expiration := ISOTime(time.Date(2023, time.August, 15, 0, 0, 0, 0, time.UTC)) + opts := &RotateProjectAccessTokenOptions{ExpiresAt: &expiration} + rotatedToken, _, err := client.ProjectAccessTokens.RotateProjectAccessToken(1, 42, opts) + if err != nil { + t.Errorf("ProjectAccessTokens.RotateProjectAccessToken returned error: %v", err) + } + + want := &ProjectAccessToken{ + ID: 42, + UserID: 1337, + Name: "Rotated Token", + Scopes: []string{"api"}, + ExpiresAt: &expiration, + CreatedAt: &createdAt, + Active: true, + Revoked: false, + AccessLevel: AccessLevelValue(30), + Token: "s3cr3t", + } + + if !reflect.DeepEqual(want, rotatedToken) { + t.Errorf("ProjectAccessTokens.RotateProjectAccessTokens returned %+v, want %+v", rotatedToken, want) + } +} + func TestRevokeProjectAccessToken(t *testing.T) { mux, client := setup(t) diff --git a/testdata/rotate_group_access_token.json b/testdata/rotate_group_access_token.json new file mode 100644 index 000000000..b8dd89c0c --- /dev/null +++ b/testdata/rotate_group_access_token.json @@ -0,0 +1,13 @@ +{ + "id": 42, + "name": "Rotated Token", + "revoked": false, + "created_at": "2023-08-01T15:00:00.000Z", + "scopes": ["api"], + "user_id": 1337, + "last_used_at": null, + "active": true, + "expires_at": "2023-08-15", + "access_level": 30, + "token": "s3cr3t" +} diff --git a/testdata/rotate_personal_access_token.json b/testdata/rotate_personal_access_token.json new file mode 100644 index 000000000..2d7b27353 --- /dev/null +++ b/testdata/rotate_personal_access_token.json @@ -0,0 +1,12 @@ +{ + "id": 42, + "name": "Rotated Token", + "revoked": false, + "created_at": "2023-08-01T15:00:00.000Z", + "scopes": ["api"], + "user_id": 1337, + "last_used_at": null, + "active": true, + "expires_at": "2023-08-15", + "token": "s3cr3t" +} diff --git a/testdata/rotate_project_access_token.json b/testdata/rotate_project_access_token.json new file mode 100644 index 000000000..b8dd89c0c --- /dev/null +++ b/testdata/rotate_project_access_token.json @@ -0,0 +1,13 @@ +{ + "id": 42, + "name": "Rotated Token", + "revoked": false, + "created_at": "2023-08-01T15:00:00.000Z", + "scopes": ["api"], + "user_id": 1337, + "last_used_at": null, + "active": true, + "expires_at": "2023-08-15", + "access_level": 30, + "token": "s3cr3t" +}