Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
56 changes: 42 additions & 14 deletions coderd/httpmw/userparam.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,60 @@ func UserParam(r *http.Request) database.User {
func ExtractUserParam(db database.Store) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
var userID uuid.UUID
if chi.URLParam(r, "user") == "me" {
userID = APIKey(r).UserID
var user database.User
var err error

// userQuery is either a uuid, a username, or 'me'
userQuery := chi.URLParam(r, "user")
if userQuery == "" {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: "\"user\" must be provided",
})
return
}

if userQuery == "me" {
user, err = db.GetUserByID(r.Context(), APIKey(r).UserID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get user: %s", err.Error()),
})
return
}
} else if userID, err := uuid.Parse(userQuery); err == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might reduce complexity if we just disallow getting by UUID entirely. I'm not sure it provides a ton of value anyways, because you need to get the user to get the ID. You could of course list users to do that, but then just use the username.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not inclined to drop the functionality because usernames might be able to be changed. UUIDs never will.

It's really nice to have a ID, as you can assume that will be constant no matter what.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough fair enough

// If the userQuery is a valid uuid
user, err = db.GetUserByID(r.Context(), userID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get user: %s", err.Error()),
})
return
}
} else {
var ok bool
userID, ok = parseUUID(rw, r, "user")
if !ok {
// Try as a username last
user, err = db.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
Username: userQuery,
})
if err != nil {
// If the error is no rows, they might have inputted something
// that is not a username or uuid. Regardless, let's not indicate if
// the user exists or not. Just lump all these errors into
// something generic.
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: "\"user\" must be a uuid or username",
})
return
}
}

apiKey := APIKey(r)
if apiKey.UserID != userID {
if apiKey.UserID != user.ID {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: "getting non-personal users isn't supported yet",
})
return
}

user, err := db.GetUserByID(r.Context(), userID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get user: %s", err.Error()),
})
}

ctx := context.WithValue(r.Context(), userParamContextKey{}, user)
next.ServeHTTP(rw, r.WithContext(ctx))
})
Expand Down
43 changes: 37 additions & 6 deletions coderd/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,14 +431,45 @@ func TestPutUserSuspend(t *testing.T) {
})
}

func TestUserByName(t *testing.T) {
func TestGetUser(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
firstUser := coderdtest.CreateFirstUser(t, client)
user, err := client.User(context.Background(), codersdk.Me)

require.NoError(t, err)
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
t.Run("ByMe", func(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, nil)
firstUser := coderdtest.CreateFirstUser(t, client)

user, err := client.User(context.Background(), codersdk.Me)
require.NoError(t, err)
require.Equal(t, firstUser.UserID, user.ID)
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
})

t.Run("ByID", func(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, nil)
firstUser := coderdtest.CreateFirstUser(t, client)

user, err := client.User(context.Background(), firstUser.UserID)
require.NoError(t, err)
require.Equal(t, firstUser.UserID, user.ID)
require.Equal(t, firstUser.OrganizationID, user.OrganizationIDs[0])
})

t.Run("ByUsername", func(t *testing.T) {
t.Parallel()

client := coderdtest.New(t, nil)
firstUser := coderdtest.CreateFirstUser(t, client)
exp, err := client.User(context.Background(), firstUser.UserID)
require.NoError(t, err)

user, err := client.UserByUsername(context.Background(), exp.Username)
require.NoError(t, err)
require.Equal(t, exp, user)
})
}

func TestGetUsers(t *testing.T) {
Expand Down
11 changes: 10 additions & 1 deletion codersdk/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,16 @@ func (c *Client) Logout(ctx context.Context) error {
// User returns a user for the ID provided.
// If the uuid is nil, the current user will be returned.
func (c *Client) User(ctx context.Context, id uuid.UUID) (User, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", uuidOrMe(id)), nil)
return c.userByIdentifier(ctx, uuidOrMe(id))
}

// UserByUsername returns a user for the username provided.
func (c *Client) UserByUsername(ctx context.Context, username string) (User, error) {
return c.userByIdentifier(ctx, username)
}

func (c *Client) userByIdentifier(ctx context.Context, ident string) (User, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/users/%s", ident), nil)
if err != nil {
return User{}, err
}
Expand Down