From d293a2b9d6722dffde7998c953c3087e47a38a83 Mon Sep 17 00:00:00 2001 From: zeripath Date: Fri, 7 Sep 2018 04:31:29 +0100 Subject: [PATCH] Add sudo functionality to the API (#4809) --- docs/content/doc/advanced/api-usage.en-us.md | 4 ++ integrations/api_admin_test.go | 29 +++++++++++++ routers/api/v1/api.go | 45 +++++++++++++++++++- templates/swagger/v1_json.tmpl | 18 ++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/docs/content/doc/advanced/api-usage.en-us.md b/docs/content/doc/advanced/api-usage.en-us.md index f04a99f14..369bae6ca 100644 --- a/docs/content/doc/advanced/api-usage.en-us.md +++ b/docs/content/doc/advanced/api-usage.en-us.md @@ -73,3 +73,7 @@ using BasicAuth, as follows: $ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens [{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}] ``` + +## Sudo + +The API allows admin users to sudo API requests as another user. Simply add either a `sudo=` parameter or `Sudo:` request header with the username of the user to sudo. diff --git a/integrations/api_admin_test.go b/integrations/api_admin_test.go index 37e5fd199..ab878dd6a 100644 --- a/integrations/api_admin_test.go +++ b/integrations/api_admin_test.go @@ -9,6 +9,8 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/assert" + "code.gitea.io/gitea/models" api "code.gitea.io/sdk/gitea" ) @@ -71,3 +73,30 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { adminUsername, newPublicKey.ID) session.MakeRequest(t, req, http.StatusForbidden) } + +func TestAPISudoUser(t *testing.T) { + prepareTestEnv(t) + adminUsername := "user1" + normalUsername := "user2" + session := loginUser(t, adminUsername) + + urlStr := fmt.Sprintf("/api/v1/user?sudo=%s", normalUsername) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + var user api.User + DecodeJSON(t, resp, &user) + + assert.Equal(t, normalUsername, user.UserName) +} + +func TestAPISudoUserForbidden(t *testing.T) { + prepareTestEnv(t) + adminUsername := "user1" + normalUsername := "user2" + + session := loginUser(t, normalUsername) + + urlStr := fmt.Sprintf("/api/v1/user?sudo=%s", adminUsername) + req := NewRequest(t, "GET", urlStr) + session.MakeRequest(t, req, http.StatusForbidden) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 47a8edab4..967db3b01 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -24,6 +24,8 @@ // - Token : // - AccessToken : // - AuthorizationHeaderToken : +// - SudoParam : +// - SudoHeader : // // SecurityDefinitions: // BasicAuth: @@ -40,6 +42,16 @@ // type: apiKey // name: Authorization // in: header +// SudoParam: +// type: apiKey +// name: sudo +// in: query +// description: Sudo API request as the user provided as the key. Admin privileges are required. +// SudoHeader: +// type: apiKey +// name: Sudo +// in: header +// description: Sudo API request as the user provided as the key. Admin privileges are required. // // swagger:meta package v1 @@ -50,6 +62,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/api/v1/admin" "code.gitea.io/gitea/routers/api/v1/misc" @@ -64,6 +77,36 @@ import ( "gopkg.in/macaron.v1" ) +func sudo() macaron.Handler { + return func(ctx *context.APIContext) { + sudo := ctx.Query("sudo") + if len(sudo) <= 0 { + sudo = ctx.Req.Header.Get("Sudo") + } + + if len(sudo) > 0 { + if ctx.User.IsAdmin { + user, err := models.GetUserByName(sudo) + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.Status(404) + } else { + ctx.Error(500, "GetUserByName", err) + } + return + } + log.Trace("Sudo from (%s) to: %s", ctx.User.Name, user.Name) + ctx.User = user + } else { + ctx.JSON(403, map[string]string{ + "message": "Only administrators allowed to sudo.", + }) + return + } + } + } +} + func repoAssignment() macaron.Handler { return func(ctx *context.APIContext) { userName := ctx.Params(":username") @@ -589,5 +632,5 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/topics", func() { m.Get("/search", repo.TopicSearch) }) - }, context.APIContexter()) + }, context.APIContexter(), sudo()) } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 598813bfc..b4b65563d 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -8008,6 +8008,18 @@ "BasicAuth": { "type": "basic" }, + "SudoHeader": { + "description": "Sudo API request as the user provided as the key. Admin privileges are required.", + "type": "apiKey", + "name": "Sudo", + "in": "header" + }, + "SudoParam": { + "description": "Sudo API request as the user provided as the key. Admin privileges are required.", + "type": "apiKey", + "name": "sudo", + "in": "query" + }, "Token": { "type": "apiKey", "name": "token", @@ -8026,6 +8038,12 @@ }, { "AuthorizationHeaderToken": [] + }, + { + "SudoParam": [] + }, + { + "SudoHeader": [] } ] } \ No newline at end of file