@@ -73,3 +73,7 @@ using BasicAuth, as follows: | |||||
$ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens | $ curl --request GET --url https://yourusername:yourpassword@gitea.your.host/api/v1/users/yourusername/tokens | ||||
[{"name":"test","sha1":"..."},{"name":"dev","sha1":"..."}] | [{"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. |
@@ -9,6 +9,8 @@ import ( | |||||
"net/http" | "net/http" | ||||
"testing" | "testing" | ||||
"github.com/stretchr/testify/assert" | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
api "code.gitea.io/sdk/gitea" | api "code.gitea.io/sdk/gitea" | ||||
) | ) | ||||
@@ -71,3 +73,30 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { | |||||
adminUsername, newPublicKey.ID) | adminUsername, newPublicKey.ID) | ||||
session.MakeRequest(t, req, http.StatusForbidden) | 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) | |||||
} |
@@ -24,6 +24,8 @@ | |||||
// - Token : | // - Token : | ||||
// - AccessToken : | // - AccessToken : | ||||
// - AuthorizationHeaderToken : | // - AuthorizationHeaderToken : | ||||
// - SudoParam : | |||||
// - SudoHeader : | |||||
// | // | ||||
// SecurityDefinitions: | // SecurityDefinitions: | ||||
// BasicAuth: | // BasicAuth: | ||||
@@ -40,6 +42,16 @@ | |||||
// type: apiKey | // type: apiKey | ||||
// name: Authorization | // name: Authorization | ||||
// in: header | // 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 | // swagger:meta | ||||
package v1 | package v1 | ||||
@@ -50,6 +62,7 @@ import ( | |||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/auth" | "code.gitea.io/gitea/modules/auth" | ||||
"code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"code.gitea.io/gitea/routers/api/v1/admin" | "code.gitea.io/gitea/routers/api/v1/admin" | ||||
"code.gitea.io/gitea/routers/api/v1/misc" | "code.gitea.io/gitea/routers/api/v1/misc" | ||||
@@ -64,6 +77,36 @@ import ( | |||||
"gopkg.in/macaron.v1" | "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 { | func repoAssignment() macaron.Handler { | ||||
return func(ctx *context.APIContext) { | return func(ctx *context.APIContext) { | ||||
userName := ctx.Params(":username") | userName := ctx.Params(":username") | ||||
@@ -589,5 +632,5 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
m.Group("/topics", func() { | m.Group("/topics", func() { | ||||
m.Get("/search", repo.TopicSearch) | m.Get("/search", repo.TopicSearch) | ||||
}) | }) | ||||
}, context.APIContexter()) | |||||
}, context.APIContexter(), sudo()) | |||||
} | } |
@@ -8008,6 +8008,18 @@ | |||||
"BasicAuth": { | "BasicAuth": { | ||||
"type": "basic" | "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": { | "Token": { | ||||
"type": "apiKey", | "type": "apiKey", | ||||
"name": "token", | "name": "token", | ||||
@@ -8026,6 +8038,12 @@ | |||||
}, | }, | ||||
{ | { | ||||
"AuthorizationHeaderToken": [] | "AuthorizationHeaderToken": [] | ||||
}, | |||||
{ | |||||
"SudoParam": [] | |||||
}, | |||||
{ | |||||
"SudoHeader": [] | |||||
} | } | ||||
] | ] | ||||
} | } |