diff --git a/models/oauth2_application.go b/models/oauth2_application.go index dd80a79b485..1e69dd64304 100644 --- a/models/oauth2_application.go +++ b/models/oauth2_application.go @@ -340,12 +340,13 @@ func getOAuth2AuthorizationByCode(e Engine, code string) (auth *OAuth2Authorizat // OAuth2Grant represents the permission of an user for a specifc application to access resources type OAuth2Grant struct { - ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"INDEX unique(user_application)"` - ApplicationID int64 `xorm:"INDEX unique(user_application)"` - Counter int64 `xorm:"NOT NULL DEFAULT 1"` - CreatedUnix util.TimeStamp `xorm:"created"` - UpdatedUnix util.TimeStamp `xorm:"updated"` + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"INDEX unique(user_application)"` + Application *OAuth2Application `xorm:"-"` + ApplicationID int64 `xorm:"INDEX unique(user_application)"` + Counter int64 `xorm:"NOT NULL DEFAULT 1"` + CreatedUnix util.TimeStamp `xorm:"created"` + UpdatedUnix util.TimeStamp `xorm:"updated"` } // TableName sets the table name to `oauth2_grant` @@ -410,6 +411,48 @@ func getOAuth2GrantByID(e Engine, id int64) (grant *OAuth2Grant, err error) { return } +// GetOAuth2GrantsByUserID lists all grants of a certain user +func GetOAuth2GrantsByUserID(uid int64) ([]*OAuth2Grant, error) { + return getOAuth2GrantsByUserID(x, uid) +} + +func getOAuth2GrantsByUserID(e Engine, uid int64) ([]*OAuth2Grant, error) { + type joinedOAuth2Grant struct { + Grant *OAuth2Grant `xorm:"extends"` + Application *OAuth2Application `xorm:"extends"` + } + var results *xorm.Rows + var err error + if results, err = e. + Table("oauth2_grant"). + Where("user_id = ?", uid). + Join("INNER", "oauth2_application", "application_id = oauth2_application.id"). + Rows(new(joinedOAuth2Grant)); err != nil { + return nil, err + } + defer results.Close() + grants := make([]*OAuth2Grant, 0) + for results.Next() { + joinedGrant := new(joinedOAuth2Grant) + if err := results.Scan(joinedGrant); err != nil { + return nil, err + } + joinedGrant.Grant.Application = joinedGrant.Application + grants = append(grants, joinedGrant.Grant) + } + return grants, nil +} + +// RevokeOAuth2Grant deletes the grant with grantID and userID +func RevokeOAuth2Grant(grantID, userID int64) error { + return revokeOAuth2Grant(x, grantID, userID) +} + +func revokeOAuth2Grant(e Engine, grantID, userID int64) error { + _, err := e.Delete(&OAuth2Grant{ID: grantID, UserID: userID}) + return err +} + ////////////////////////////////////////////////////////////// // OAuth2TokenType represents the type of token for an oauth application diff --git a/models/oauth2_application_test.go b/models/oauth2_application_test.go index b06d9356c0b..3afdf50f53b 100644 --- a/models/oauth2_application_test.go +++ b/models/oauth2_application_test.go @@ -135,6 +135,25 @@ func TestOAuth2Grant_TableName(t *testing.T) { assert.Equal(t, "oauth2_grant", new(OAuth2Grant).TableName()) } +func TestGetOAuth2GrantsByUserID(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + result, err := GetOAuth2GrantsByUserID(1) + assert.NoError(t, err) + assert.Len(t, result, 1) + assert.Equal(t, int64(1), result[0].ID) + assert.Equal(t, result[0].ApplicationID, result[0].Application.ID) + + result, err = GetOAuth2GrantsByUserID(34134) + assert.NoError(t, err) + assert.Empty(t, result) +} + +func TestRevokeOAuth2Grant(t *testing.T) { + assert.NoError(t, PrepareTestDatabase()) + assert.NoError(t, RevokeOAuth2Grant(1, 1)) + AssertNotExistsBean(t, &OAuth2Grant{ID: 1, UserID: 1}) +} + //////////////////// Authorization Code func TestGetOAuth2AuthorizationByCode(t *testing.T) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 23d19492032..9fc1e3da2a1 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -499,6 +499,13 @@ oauth2_application_edit = Edit oauth2_application_create_description = OAuth2 applications gives your third-party application access to user accounts on this instance. oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue? +authorized_oauth2_applications = Authorized OAuth2 Applications +authorized_oauth2_applications_description = You've granted access to your personal Gitea account to these third party applications. Please revoke access for applications no longer needed. +revoke_key = Revoke +revoke_oauth2_grant = Revoke Access +revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure? +revoke_oauth2_grant_success = You've revoked access successfully. + twofa_desc = Two-factor authentication enhances the security of your account. twofa_is_enrolled = Your account is currently enrolled in two-factor authentication. twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication. diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 5c6d36befae..9602bbed4a9 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -344,6 +344,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/:id/regenerate_secret", userSetting.OAuthApplicationsRegenerateSecret) m.Post("", bindIgnErr(auth.EditOAuth2ApplicationForm{}), userSetting.OAuthApplicationsPost) m.Post("/delete", userSetting.DeleteOAuth2Application) + m.Post("/revoke", userSetting.RevokeOAuth2Grant) }) m.Combo("/applications").Get(userSetting.Applications). Post(bindIgnErr(auth.NewAccessTokenForm{}), userSetting.ApplicationsPost) diff --git a/routers/user/setting/applications.go b/routers/user/setting/applications.go index bc8633f72d6..90e34d9e1a3 100644 --- a/routers/user/setting/applications.go +++ b/routers/user/setting/applications.go @@ -81,5 +81,10 @@ func loadApplicationsData(ctx *context.Context) { ctx.ServerError("GetOAuth2ApplicationsByUserID", err) return } + ctx.Data["Grants"], err = models.GetOAuth2GrantsByUserID(ctx.User.ID) + if err != nil { + ctx.ServerError("GetOAuth2GrantsByUserID", err) + return + } } } diff --git a/routers/user/setting/oauth2.go b/routers/user/setting/oauth2.go index 1068b5db49b..265e32642b0 100644 --- a/routers/user/setting/oauth2.go +++ b/routers/user/setting/oauth2.go @@ -5,6 +5,8 @@ package setting import ( + "fmt" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" @@ -138,3 +140,20 @@ func DeleteOAuth2Application(ctx *context.Context) { "redirect": setting.AppSubURL + "/user/settings/applications", }) } + +// RevokeOAuth2Grant revokes the grant with the given id +func RevokeOAuth2Grant(ctx *context.Context) { + if ctx.User.ID == 0 || ctx.QueryInt64("id") == 0 { + ctx.ServerError("RevokeOAuth2Grant", fmt.Errorf("user id or grant id is zero")) + return + } + if err := models.RevokeOAuth2Grant(ctx.QueryInt64("id"), ctx.User.ID); err != nil { + ctx.ServerError("RevokeOAuth2Grant", err) + return + } + + ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success")) + ctx.JSON(200, map[string]interface{}{ + "redirect": setting.AppSubURL + "/user/settings/applications", + }) +} diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 1a3ab5efaf6..08b2ca71952 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -47,6 +47,7 @@ {{if .EnableOAuth2}} + {{template "user/settings/grants_oauth2" .}} {{template "user/settings/applications_oauth2" .}} {{end}} diff --git a/templates/user/settings/grants_oauth2.tmpl b/templates/user/settings/grants_oauth2.tmpl new file mode 100644 index 00000000000..4dc5b9446b7 --- /dev/null +++ b/templates/user/settings/grants_oauth2.tmpl @@ -0,0 +1,39 @@ +

+ {{.i18n.Tr "settings.authorized_oauth2_applications"}} +

+
+
+
+ {{.i18n.Tr "settings.authorized_oauth2_applications_description"}} +
+ {{range $grant := .Grants}} +
+
+ +
+ +
+ {{$grant.Application.Name}} +
+ {{$.i18n.Tr "settings.add_on"}} {{$grant.CreatedUnix.FormatShort}} +
+
+
+ {{end}} +
+
+ +