diff --git a/integrations/api_oauth2_apps_test.go b/integrations/api_oauth2_apps_test.go index fe7958230..998043a6f 100644 --- a/integrations/api_oauth2_apps_test.go +++ b/integrations/api_oauth2_apps_test.go @@ -19,6 +19,8 @@ func TestOAuth2Application(t *testing.T) { defer prepareTestEnv(t)() testAPICreateOAuth2Application(t) testAPIListOAuth2Applications(t) + testAPIGetOAuth2Application(t) + testAPIUpdateOAuth2Application(t) testAPIDeleteOAuth2Application(t) } @@ -83,9 +85,6 @@ func testAPIDeleteOAuth2Application(t *testing.T) { oldApp := models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ UID: user.ID, Name: "test-app-1", - RedirectURIs: []string{ - "http://www.google.com", - }, }).(*models.OAuth2Application) urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d?token=%s", oldApp.ID, token) @@ -94,3 +93,67 @@ func testAPIDeleteOAuth2Application(t *testing.T) { models.AssertNotExistsBean(t, &models.OAuth2Application{UID: oldApp.UID, Name: oldApp.Name}) } + +func testAPIGetOAuth2Application(t *testing.T) { + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session) + + existApp := models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ + UID: user.ID, + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com", + }, + }).(*models.OAuth2Application) + + urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d?token=%s", existApp.ID, token) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + + var app api.OAuth2Application + DecodeJSON(t, resp, &app) + expectedApp := app + + assert.EqualValues(t, existApp.Name, expectedApp.Name) + assert.EqualValues(t, existApp.ClientID, expectedApp.ClientID) + assert.Len(t, expectedApp.ClientID, 36) + assert.Empty(t, expectedApp.ClientSecret) + assert.EqualValues(t, len(expectedApp.RedirectURIs), 1) + assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) + models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) +} + +func testAPIUpdateOAuth2Application(t *testing.T) { + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) + + existApp := models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ + UID: user.ID, + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com", + }, + }).(*models.OAuth2Application) + + appBody := api.CreateOAuth2ApplicationOptions{ + Name: "test-app-1", + RedirectURIs: []string{ + "http://www.google.com/", + "http://www.github.com/", + }, + } + + urlStr := fmt.Sprintf("/api/v1/user/applications/oauth2/%d", existApp.ID) + req := NewRequestWithJSON(t, "PATCH", urlStr, &appBody) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var app api.OAuth2Application + DecodeJSON(t, resp, &app) + expectedApp := app + + assert.EqualValues(t, len(expectedApp.RedirectURIs), 2) + assert.EqualValues(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0]) + assert.EqualValues(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1]) + models.AssertExistsAndLoadBean(t, &models.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 150c073c9..bce3bf245 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -580,7 +580,10 @@ func RegisterRoutes(m *macaron.Macaron) { m.Combo("/oauth2"). Get(user.ListOauth2Applications). Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application) - m.Delete("/oauth2/:id", user.DeleteOauth2Application) + m.Combo("/oauth2/:id"). + Delete(user.DeleteOauth2Application). + Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application). + Get(user.GetOauth2Application) }, reqToken()) m.Group("/gpg_keys", func() { diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 7e0e620fe..9ec506bcf 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -231,3 +231,89 @@ func DeleteOauth2Application(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } + +// GetOauth2Application get OAuth2 Application +func GetOauth2Application(ctx *context.APIContext) { + // swagger:operation GET /user/applications/oauth2/{id} user userGetOAuth2Application + // --- + // summary: get an OAuth2 Application + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: Application ID to be found + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/OAuth2Application" + appID := ctx.ParamsInt64(":id") + app, err := models.GetOAuth2ApplicationByID(appID) + if err != nil { + if models.IsErrOauthClientIDInvalid(err) || models.IsErrOAuthApplicationNotFound(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "GetOauth2ApplicationByID", err) + } + return + } + + app.ClientSecret = "" + + ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) +} + +// UpdateOauth2Application update OAuth2 Application +func UpdateOauth2Application(ctx *context.APIContext, data api.CreateOAuth2ApplicationOptions) { + // swagger:operation PATCH /user/applications/oauth2/{id} user userUpdateOAuth2Application + // --- + // summary: update an OAuth2 Application, this includes regenerating the client secret + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: application to be updated + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/CreateOAuth2ApplicationOptions" + // responses: + // "200": + // "$ref": "#/responses/OAuth2Application" + appID := ctx.ParamsInt64(":id") + + err := models.UpdateOAuth2Application(models.UpdateOAuth2ApplicationOptions{ + Name: data.Name, + UserID: ctx.User.ID, + ID: appID, + RedirectURIs: data.RedirectURIs, + }) + if err != nil { + ctx.Error(http.StatusBadRequest, "", "error updating oauth2 application") + return + } + app, err := models.GetOAuth2ApplicationByID(appID) + if err != nil { + if models.IsErrOauthClientIDInvalid(err) || models.IsErrOAuthApplicationNotFound(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "UpdateOauth2ApplicationByID", err) + } + return + } + secret, err := app.GenerateClientSecret() + if err != nil { + ctx.Error(http.StatusBadRequest, "", "error updating application secret") + return + } + app.ClientSecret = secret + + ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 55094e391..8591381ef 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -8360,6 +8360,31 @@ } }, "/user/applications/oauth2/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "get an OAuth2 Application", + "operationId": "userGetOAuth2Application", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "Application ID to be found", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/OAuth2Application" + } + } + }, "delete": { "produces": [ "application/json" @@ -8384,6 +8409,39 @@ "$ref": "#/responses/empty" } } + }, + "patch": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "update an OAuth2 Application, this includes regenerating the client secret", + "operationId": "userUpdateOAuth2Application", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "application to be updated", + "name": "id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CreateOAuth2ApplicationOptions" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/OAuth2Application" + } + } } }, "/user/emails": {