Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions bitbucket/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package bitbucket

import (
"context"

forge "github.com/git-pkgs/forge"
)

type bbUser struct {
Username string `json:"username"`
DisplayName string `json:"display_name"`
Links struct {
HTML struct {
Href string `json:"href"`
} `json:"html"`
Avatar struct {
Href string `json:"href"`
} `json:"avatar"`
} `json:"links"`
}

func (f *bitbucketForge) GetAuthenticatedUser(ctx context.Context) (*forge.User, error) {
rs := &bitbucketRepoService{token: f.token, httpClient: f.httpClient}
var bb bbUser
if err := rs.getJSON(ctx, bitbucketAPI+"/user", &bb); err != nil {
return nil, err
}

// Bitbucket only returns email addresses from the separate /user/emails
// endpoint, so Email is left empty.
return &forge.User{
Login: bb.Username,
Name: bb.DisplayName,
AvatarURL: bb.Links.Avatar.Href,
HTMLURL: bb.Links.HTML.Href,
}, nil
}
65 changes: 65 additions & 0 deletions bitbucket/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package bitbucket

import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"

forge "github.com/git-pkgs/forge"
)

func TestBitbucketGetAuthenticatedUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /2.0/user", func(w http.ResponseWriter, r *http.Request) {
assertEqual(t, "Authorization", "Bearer test-token", r.Header.Get("Authorization"))
_ = json.NewEncoder(w).Encode(map[string]any{
"username": "alice",
"display_name": "Alice Example",
"links": map[string]any{
"html": map[string]string{"href": "https://bitbucket.org/alice/"},
"avatar": map[string]string{"href": "https://bitbucket.org/account/alice/avatar/"},
},
})
})

srv := httptest.NewServer(mux)
defer srv.Close()
origAPI := bitbucketAPI
setBitbucketAPI(srv.URL + "/2.0")
defer setBitbucketAPI(origAPI)

f := New("test-token", srv.Client())
u, err := f.GetAuthenticatedUser(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

assertEqual(t, "Login", "alice", u.Login)
assertEqual(t, "Name", "Alice Example", u.Name)
assertEqual(t, "Email", "", u.Email)
assertEqual(t, "AvatarURL", "https://bitbucket.org/account/alice/avatar/", u.AvatarURL)
assertEqual(t, "HTMLURL", "https://bitbucket.org/alice/", u.HTMLURL)
}

func TestBitbucketGetAuthenticatedUserUnauthorized(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /2.0/user", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
})

srv := httptest.NewServer(mux)
defer srv.Close()
origAPI := bitbucketAPI
setBitbucketAPI(srv.URL + "/2.0")
defer setBitbucketAPI(origAPI)

f := New("", srv.Client())
_, err := f.GetAuthenticatedUser(context.Background())
var httpErr *forge.HTTPError
if !errors.As(err, &httpErr) || httpErr.StatusCode != http.StatusUnauthorized {
t.Fatalf("expected HTTP 401 error, got %v", err)
}
}
3 changes: 3 additions & 0 deletions forge.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ type Forge interface {
CheckRuns() CheckRunService
Commits() CommitService
GetRateLimit(ctx context.Context) (*RateLimit, error)
// GetAuthenticatedUser returns the profile of the user the client's
// token belongs to.
GetAuthenticatedUser(ctx context.Context) (*User, error)
// ParsePath parses URL path segments into a resource reference.
ParsePath(pathParts []string) (*ResourceRef, error)
}
Expand Down
4 changes: 4 additions & 0 deletions forges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,10 @@ func (m *mockForge) GetRateLimit(_ context.Context) (*RateLimit, error) {
return nil, ErrNotSupported
}

func (m *mockForge) GetAuthenticatedUser(_ context.Context) (*User, error) {
return nil, ErrNotSupported
}

func (m *mockForge) ParsePath(_ []string) (*ResourceRef, error) {
return &ResourceRef{}, nil
}
Expand Down
6 changes: 3 additions & 3 deletions gerrit/changes.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (f *gerritForge) PullRequests() forge.PullRequestService {
return &gerritPRService{forge: f}
}

func (s *gerritPRService) convertAccount(a *gerritAccountInfo) forge.User {
func convertAccount(a *gerritAccountInfo) forge.User {
if a == nil {
return forge.User{}
}
Expand Down Expand Up @@ -100,7 +100,7 @@ func (s *gerritPRService) convertChange(c gerritChangeInfo) forge.PullRequest {
Title: c.Subject,
State: state,
Draft: c.WorkInProgress,
Author: s.convertAccount(c.Owner),
Author: convertAccount(c.Owner),
Head: head,
Base: forge.PRBranch{Ref: c.Branch},
Merged: merged,
Expand Down Expand Up @@ -261,7 +261,7 @@ func (s *gerritPRService) ListComments(ctx context.Context, owner, repo string,
for _, msg := range messages {
comments = append(comments, forge.Comment{
Body: msg.Message,
Author: s.convertAccount(msg.Author),
Author: convertAccount(msg.Author),
HTMLURL: s.changeURL(projectName(owner, repo), number),
CreatedAt: parseGerritTime(msg.Date),
UpdatedAt: parseGerritTime(msg.Date),
Expand Down
18 changes: 18 additions & 0 deletions gerrit/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gerrit

import (
"context"
"net/http"

forge "github.com/git-pkgs/forge"
)

func (f *gerritForge) GetAuthenticatedUser(ctx context.Context) (*forge.User, error) {
var account gerritAccountInfo
if err := f.doJSON(ctx, http.MethodGet, "/accounts/self", nil, nil, &account); err != nil {
return nil, err
}

user := convertAccount(&account)
return &user, nil
}
36 changes: 36 additions & 0 deletions gerrit/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gerrit

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
)

func TestGetAuthenticatedUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /a/accounts/self", func(w http.ResponseWriter, r *http.Request) {
user, password, ok := r.BasicAuth()
if !ok || user != "alice" || password != "secret" {
t.Errorf("basic auth = %q, %q, %v; want alice, secret, true", user, password, ok)
}
_, _ = fmt.Fprint(w, xssi+`{
"_account_id": 1000096,
"name": "Alice Example",
"email": "alice@example.com",
"username": "alice"
}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "alice:secret", srv.Client())
u, err := f.GetAuthenticatedUser(context.Background())
if err != nil {
t.Fatalf("GetAuthenticatedUser: %v", err)
}
if u.Login != "alice" || u.Name != "Alice Example" || u.Email != "alice@example.com" {
t.Fatalf("unexpected user: %+v", u)
}
}
22 changes: 22 additions & 0 deletions gitea/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package gitea

import (
"context"

forge "github.com/git-pkgs/forge"
)

func (f *giteaForge) GetAuthenticatedUser(ctx context.Context) (*forge.User, error) {
u, resp, err := f.client.GetMyUserInfo()
if err != nil {
return nil, wrapErr("get authenticated user", resp, err)
}

return &forge.User{
Login: u.UserName,
Name: u.FullName,
Email: u.Email,
AvatarURL: u.AvatarURL,
HTMLURL: u.HTMLURL,
}, nil
}
65 changes: 65 additions & 0 deletions gitea/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gitea

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)

func TestGiteaGetAuthenticatedUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/user", func(w http.ResponseWriter, r *http.Request) {
assertEqual(t, "Authorization", "token test-token", r.Header.Get("Authorization"))
_ = json.NewEncoder(w).Encode(map[string]any{
"id": 1,
"login": "alice",
"full_name": "Alice Example",
"email": "alice@example.com",
"avatar_url": "https://codeberg.org/avatars/alice",
"html_url": "https://codeberg.org/alice",
})
})

srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "test-token", nil)
u, err := f.GetAuthenticatedUser(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

assertEqual(t, "Login", "alice", u.Login)
assertEqual(t, "Name", "Alice Example", u.Name)
assertEqual(t, "Email", "alice@example.com", u.Email)
assertEqual(t, "AvatarURL", "https://codeberg.org/avatars/alice", u.AvatarURL)
assertEqual(t, "HTMLURL", "https://codeberg.org/alice", u.HTMLURL)
}

func TestGiteaGetAuthenticatedUserUnauthorized(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/version", giteaVersionHandler)
mux.HandleFunc("GET /api/v1/user", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"message":""}`))
})

srv := httptest.NewServer(mux)
defer srv.Close()

f := New(srv.URL, "expired-token", nil)
_, err := f.GetAuthenticatedUser(context.Background())
if err == nil {
t.Fatal("expected error for expired token")
}
for _, want := range []string{"get authenticated user", "401"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("error %q does not contain %q", err, want)
}
}
}
23 changes: 23 additions & 0 deletions github/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package github

import (
"context"

forge "github.com/git-pkgs/forge"
)

func (f *gitHubForge) GetAuthenticatedUser(ctx context.Context) (*forge.User, error) {
// An empty username asks GitHub for the authenticated user (GET /user).
u, _, err := f.client.Users.Get(ctx, "")
if err != nil {
return nil, err
}

return &forge.User{
Login: u.GetLogin(),
Name: u.GetName(),
Email: u.GetEmail(),
AvatarURL: u.GetAvatarURL(),
HTMLURL: u.GetHTMLURL(),
}, nil
}
63 changes: 63 additions & 0 deletions github/user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package github

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/google/go-github/v82/github"
)

func TestGitHubGetAuthenticatedUser(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v3/user", func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"login": "octocat",
"name": "The Octocat",
"email": "octocat@github.com",
"avatar_url": "https://avatars.githubusercontent.com/u/583231",
"html_url": "https://github.com/octocat",
"type": "User",
})
})

srv := httptest.NewServer(mux)
defer srv.Close()

c := github.NewClient(nil)
c, _ = c.WithEnterpriseURLs(srv.URL+"/api/v3", srv.URL+"/api/v3")
f := &gitHubForge{client: c}

u, err := f.GetAuthenticatedUser(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

assertEqual(t, "Login", "octocat", u.Login)
assertEqual(t, "Name", "The Octocat", u.Name)
assertEqual(t, "Email", "octocat@github.com", u.Email)
assertEqual(t, "AvatarURL", "https://avatars.githubusercontent.com/u/583231", u.AvatarURL)
assertEqual(t, "HTMLURL", "https://github.com/octocat", u.HTMLURL)
assertEqualBool(t, "IsOrg", false, u.IsOrg)
}

func TestGitHubGetAuthenticatedUserUnauthorized(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v3/user", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
_, _ = w.Write([]byte(`{"message":"Requires authentication"}`))
})

srv := httptest.NewServer(mux)
defer srv.Close()

c := github.NewClient(nil)
c, _ = c.WithEnterpriseURLs(srv.URL+"/api/v3", srv.URL+"/api/v3")
f := &gitHubForge{client: c}

if _, err := f.GetAuthenticatedUser(context.Background()); err == nil {
t.Fatal("expected error for unauthenticated request")
}
}
4 changes: 4 additions & 0 deletions gitlab/gitlab_tinygo.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ func (f *gitLabForge) GetRateLimit(context.Context) (*forge.RateLimit, error) {
return nil, errTinyGo
}

func (f *gitLabForge) GetAuthenticatedUser(context.Context) (*forge.User, error) {
return nil, errTinyGo
}

type gitLabBranchService struct{}

func (f *gitLabForge) Branches() forge.BranchService { return &gitLabBranchService{} }
Expand Down
Loading
Loading