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