code.gitea.io/gitea@v1.21.7/routers/api/v1/user/app.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package user
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    13  
    14  	auth_model "code.gitea.io/gitea/models/auth"
    15  	"code.gitea.io/gitea/modules/context"
    16  	api "code.gitea.io/gitea/modules/structs"
    17  	"code.gitea.io/gitea/modules/web"
    18  	"code.gitea.io/gitea/routers/api/v1/utils"
    19  	"code.gitea.io/gitea/services/convert"
    20  )
    21  
    22  // ListAccessTokens list all the access tokens
    23  func ListAccessTokens(ctx *context.APIContext) {
    24  	// swagger:operation GET /users/{username}/tokens user userGetTokens
    25  	// ---
    26  	// summary: List the authenticated user's access tokens
    27  	// produces:
    28  	// - application/json
    29  	// parameters:
    30  	// - name: username
    31  	//   in: path
    32  	//   description: username of user
    33  	//   type: string
    34  	//   required: true
    35  	// - name: page
    36  	//   in: query
    37  	//   description: page number of results to return (1-based)
    38  	//   type: integer
    39  	// - name: limit
    40  	//   in: query
    41  	//   description: page size of results
    42  	//   type: integer
    43  	// responses:
    44  	//   "200":
    45  	//     "$ref": "#/responses/AccessTokenList"
    46  	//   "403":
    47  	//     "$ref": "#/responses/forbidden"
    48  
    49  	opts := auth_model.ListAccessTokensOptions{UserID: ctx.ContextUser.ID, ListOptions: utils.GetListOptions(ctx)}
    50  
    51  	count, err := auth_model.CountAccessTokens(ctx, opts)
    52  	if err != nil {
    53  		ctx.InternalServerError(err)
    54  		return
    55  	}
    56  	tokens, err := auth_model.ListAccessTokens(ctx, opts)
    57  	if err != nil {
    58  		ctx.InternalServerError(err)
    59  		return
    60  	}
    61  
    62  	apiTokens := make([]*api.AccessToken, len(tokens))
    63  	for i := range tokens {
    64  		apiTokens[i] = &api.AccessToken{
    65  			ID:             tokens[i].ID,
    66  			Name:           tokens[i].Name,
    67  			TokenLastEight: tokens[i].TokenLastEight,
    68  			Scopes:         tokens[i].Scope.StringSlice(),
    69  		}
    70  	}
    71  
    72  	ctx.SetTotalCountHeader(count)
    73  	ctx.JSON(http.StatusOK, &apiTokens)
    74  }
    75  
    76  // CreateAccessToken create access tokens
    77  func CreateAccessToken(ctx *context.APIContext) {
    78  	// swagger:operation POST /users/{username}/tokens user userCreateToken
    79  	// ---
    80  	// summary: Create an access token
    81  	// consumes:
    82  	// - application/json
    83  	// produces:
    84  	// - application/json
    85  	// parameters:
    86  	// - name: username
    87  	//   in: path
    88  	//   description: username of user
    89  	//   required: true
    90  	//   type: string
    91  	// - name: body
    92  	//   in: body
    93  	//   schema:
    94  	//     "$ref": "#/definitions/CreateAccessTokenOption"
    95  	// responses:
    96  	//   "201":
    97  	//     "$ref": "#/responses/AccessToken"
    98  	//   "400":
    99  	//     "$ref": "#/responses/error"
   100  	//   "403":
   101  	//     "$ref": "#/responses/forbidden"
   102  
   103  	form := web.GetForm(ctx).(*api.CreateAccessTokenOption)
   104  
   105  	t := &auth_model.AccessToken{
   106  		UID:  ctx.ContextUser.ID,
   107  		Name: form.Name,
   108  	}
   109  
   110  	exist, err := auth_model.AccessTokenByNameExists(ctx, t)
   111  	if err != nil {
   112  		ctx.InternalServerError(err)
   113  		return
   114  	}
   115  	if exist {
   116  		ctx.Error(http.StatusBadRequest, "AccessTokenByNameExists", errors.New("access token name has been used already"))
   117  		return
   118  	}
   119  
   120  	scope, err := auth_model.AccessTokenScope(strings.Join(form.Scopes, ",")).Normalize()
   121  	if err != nil {
   122  		ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err))
   123  		return
   124  	}
   125  	t.Scope = scope
   126  
   127  	if err := auth_model.NewAccessToken(ctx, t); err != nil {
   128  		ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
   129  		return
   130  	}
   131  	ctx.JSON(http.StatusCreated, &api.AccessToken{
   132  		Name:           t.Name,
   133  		Token:          t.Token,
   134  		ID:             t.ID,
   135  		TokenLastEight: t.TokenLastEight,
   136  	})
   137  }
   138  
   139  // DeleteAccessToken delete access tokens
   140  func DeleteAccessToken(ctx *context.APIContext) {
   141  	// swagger:operation DELETE /users/{username}/tokens/{token} user userDeleteAccessToken
   142  	// ---
   143  	// summary: delete an access token
   144  	// produces:
   145  	// - application/json
   146  	// parameters:
   147  	// - name: username
   148  	//   in: path
   149  	//   description: username of user
   150  	//   type: string
   151  	//   required: true
   152  	// - name: token
   153  	//   in: path
   154  	//   description: token to be deleted, identified by ID and if not available by name
   155  	//   type: string
   156  	//   required: true
   157  	// responses:
   158  	//   "204":
   159  	//     "$ref": "#/responses/empty"
   160  	//   "403":
   161  	//     "$ref": "#/responses/forbidden"
   162  	//   "404":
   163  	//     "$ref": "#/responses/notFound"
   164  	//   "422":
   165  	//     "$ref": "#/responses/error"
   166  
   167  	token := ctx.Params(":id")
   168  	tokenID, _ := strconv.ParseInt(token, 0, 64)
   169  
   170  	if tokenID == 0 {
   171  		tokens, err := auth_model.ListAccessTokens(ctx, auth_model.ListAccessTokensOptions{
   172  			Name:   token,
   173  			UserID: ctx.ContextUser.ID,
   174  		})
   175  		if err != nil {
   176  			ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err)
   177  			return
   178  		}
   179  
   180  		switch len(tokens) {
   181  		case 0:
   182  			ctx.NotFound()
   183  			return
   184  		case 1:
   185  			tokenID = tokens[0].ID
   186  		default:
   187  			ctx.Error(http.StatusUnprocessableEntity, "DeleteAccessTokenByID", fmt.Errorf("multiple matches for token name '%s'", token))
   188  			return
   189  		}
   190  	}
   191  	if tokenID == 0 {
   192  		ctx.Error(http.StatusInternalServerError, "Invalid TokenID", nil)
   193  		return
   194  	}
   195  
   196  	if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil {
   197  		if auth_model.IsErrAccessTokenNotExist(err) {
   198  			ctx.NotFound()
   199  		} else {
   200  			ctx.Error(http.StatusInternalServerError, "DeleteAccessTokenByID", err)
   201  		}
   202  		return
   203  	}
   204  
   205  	ctx.Status(http.StatusNoContent)
   206  }
   207  
   208  // CreateOauth2Application is the handler to create a new OAuth2 Application for the authenticated user
   209  func CreateOauth2Application(ctx *context.APIContext) {
   210  	// swagger:operation POST /user/applications/oauth2 user userCreateOAuth2Application
   211  	// ---
   212  	// summary: creates a new OAuth2 application
   213  	// produces:
   214  	// - application/json
   215  	// parameters:
   216  	// - name: body
   217  	//   in: body
   218  	//   required: true
   219  	//   schema:
   220  	//     "$ref": "#/definitions/CreateOAuth2ApplicationOptions"
   221  	// responses:
   222  	//   "201":
   223  	//     "$ref": "#/responses/OAuth2Application"
   224  	//   "400":
   225  	//     "$ref": "#/responses/error"
   226  
   227  	data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
   228  
   229  	app, err := auth_model.CreateOAuth2Application(ctx, auth_model.CreateOAuth2ApplicationOptions{
   230  		Name:               data.Name,
   231  		UserID:             ctx.Doer.ID,
   232  		RedirectURIs:       data.RedirectURIs,
   233  		ConfidentialClient: data.ConfidentialClient,
   234  	})
   235  	if err != nil {
   236  		ctx.Error(http.StatusBadRequest, "", "error creating oauth2 application")
   237  		return
   238  	}
   239  	secret, err := app.GenerateClientSecret()
   240  	if err != nil {
   241  		ctx.Error(http.StatusBadRequest, "", "error creating application secret")
   242  		return
   243  	}
   244  	app.ClientSecret = secret
   245  
   246  	ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app))
   247  }
   248  
   249  // ListOauth2Applications list all the Oauth2 application
   250  func ListOauth2Applications(ctx *context.APIContext) {
   251  	// swagger:operation GET /user/applications/oauth2 user userGetOauth2Application
   252  	// ---
   253  	// summary: List the authenticated user's oauth2 applications
   254  	// produces:
   255  	// - application/json
   256  	// parameters:
   257  	// - name: page
   258  	//   in: query
   259  	//   description: page number of results to return (1-based)
   260  	//   type: integer
   261  	// - name: limit
   262  	//   in: query
   263  	//   description: page size of results
   264  	//   type: integer
   265  	// responses:
   266  	//   "200":
   267  	//     "$ref": "#/responses/OAuth2ApplicationList"
   268  
   269  	apps, total, err := auth_model.ListOAuth2Applications(ctx.Doer.ID, utils.GetListOptions(ctx))
   270  	if err != nil {
   271  		ctx.Error(http.StatusInternalServerError, "ListOAuth2Applications", err)
   272  		return
   273  	}
   274  
   275  	apiApps := make([]*api.OAuth2Application, len(apps))
   276  	for i := range apps {
   277  		apiApps[i] = convert.ToOAuth2Application(apps[i])
   278  		apiApps[i].ClientSecret = "" // Hide secret on application list
   279  	}
   280  
   281  	ctx.SetTotalCountHeader(total)
   282  	ctx.JSON(http.StatusOK, &apiApps)
   283  }
   284  
   285  // DeleteOauth2Application delete OAuth2 Application
   286  func DeleteOauth2Application(ctx *context.APIContext) {
   287  	// swagger:operation DELETE /user/applications/oauth2/{id} user userDeleteOAuth2Application
   288  	// ---
   289  	// summary: delete an OAuth2 Application
   290  	// produces:
   291  	// - application/json
   292  	// parameters:
   293  	// - name: id
   294  	//   in: path
   295  	//   description: token to be deleted
   296  	//   type: integer
   297  	//   format: int64
   298  	//   required: true
   299  	// responses:
   300  	//   "204":
   301  	//     "$ref": "#/responses/empty"
   302  	//   "404":
   303  	//     "$ref": "#/responses/notFound"
   304  	appID := ctx.ParamsInt64(":id")
   305  	if err := auth_model.DeleteOAuth2Application(appID, ctx.Doer.ID); err != nil {
   306  		if auth_model.IsErrOAuthApplicationNotFound(err) {
   307  			ctx.NotFound()
   308  		} else {
   309  			ctx.Error(http.StatusInternalServerError, "DeleteOauth2ApplicationByID", err)
   310  		}
   311  		return
   312  	}
   313  
   314  	ctx.Status(http.StatusNoContent)
   315  }
   316  
   317  // GetOauth2Application get OAuth2 Application
   318  func GetOauth2Application(ctx *context.APIContext) {
   319  	// swagger:operation GET /user/applications/oauth2/{id} user userGetOAuth2Application
   320  	// ---
   321  	// summary: get an OAuth2 Application
   322  	// produces:
   323  	// - application/json
   324  	// parameters:
   325  	// - name: id
   326  	//   in: path
   327  	//   description: Application ID to be found
   328  	//   type: integer
   329  	//   format: int64
   330  	//   required: true
   331  	// responses:
   332  	//   "200":
   333  	//     "$ref": "#/responses/OAuth2Application"
   334  	//   "404":
   335  	//     "$ref": "#/responses/notFound"
   336  	appID := ctx.ParamsInt64(":id")
   337  	app, err := auth_model.GetOAuth2ApplicationByID(ctx, appID)
   338  	if err != nil {
   339  		if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) {
   340  			ctx.NotFound()
   341  		} else {
   342  			ctx.Error(http.StatusInternalServerError, "GetOauth2ApplicationByID", err)
   343  		}
   344  		return
   345  	}
   346  	if app.UID != ctx.Doer.ID {
   347  		ctx.NotFound()
   348  		return
   349  	}
   350  
   351  	app.ClientSecret = ""
   352  
   353  	ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app))
   354  }
   355  
   356  // UpdateOauth2Application update OAuth2 Application
   357  func UpdateOauth2Application(ctx *context.APIContext) {
   358  	// swagger:operation PATCH /user/applications/oauth2/{id} user userUpdateOAuth2Application
   359  	// ---
   360  	// summary: update an OAuth2 Application, this includes regenerating the client secret
   361  	// produces:
   362  	// - application/json
   363  	// parameters:
   364  	// - name: id
   365  	//   in: path
   366  	//   description: application to be updated
   367  	//   type: integer
   368  	//   format: int64
   369  	//   required: true
   370  	// - name: body
   371  	//   in: body
   372  	//   required: true
   373  	//   schema:
   374  	//     "$ref": "#/definitions/CreateOAuth2ApplicationOptions"
   375  	// responses:
   376  	//   "200":
   377  	//     "$ref": "#/responses/OAuth2Application"
   378  	//   "404":
   379  	//     "$ref": "#/responses/notFound"
   380  	appID := ctx.ParamsInt64(":id")
   381  
   382  	data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
   383  
   384  	app, err := auth_model.UpdateOAuth2Application(auth_model.UpdateOAuth2ApplicationOptions{
   385  		Name:               data.Name,
   386  		UserID:             ctx.Doer.ID,
   387  		ID:                 appID,
   388  		RedirectURIs:       data.RedirectURIs,
   389  		ConfidentialClient: data.ConfidentialClient,
   390  	})
   391  	if err != nil {
   392  		if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) {
   393  			ctx.NotFound()
   394  		} else {
   395  			ctx.Error(http.StatusInternalServerError, "UpdateOauth2ApplicationByID", err)
   396  		}
   397  		return
   398  	}
   399  	app.ClientSecret, err = app.GenerateClientSecret()
   400  	if err != nil {
   401  		ctx.Error(http.StatusBadRequest, "", "error updating application secret")
   402  		return
   403  	}
   404  
   405  	ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app))
   406  }