OAuth2 Grant UI (#6625)

* Add oauth2 grants ui

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Add delete functionality
Add translations

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix unit tests

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Fix unit tests

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Refactor DeleteOAuth2Grant
Use results.Close()

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Refactor DeleteOAuth2Grant (again)

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Check if user ID is zero

Signed-off-by: Jonas Franz <info@jonasfranz.software>

* Check if grant ID is zero

Signed-off-by: Jonas Franz <info@jonasfranz.software>
This commit is contained in:
Jonas Franz 2019-04-17 10:18:16 +02:00 committed by Lunny Xiao
parent 34548369e1
commit 7a4c29c739
8 changed files with 140 additions and 6 deletions

View file

@ -342,6 +342,7 @@ func getOAuth2AuthorizationByCode(e Engine, code string) (auth *OAuth2Authorizat
type OAuth2Grant struct { type OAuth2Grant struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"INDEX unique(user_application)"` UserID int64 `xorm:"INDEX unique(user_application)"`
Application *OAuth2Application `xorm:"-"`
ApplicationID int64 `xorm:"INDEX unique(user_application)"` ApplicationID int64 `xorm:"INDEX unique(user_application)"`
Counter int64 `xorm:"NOT NULL DEFAULT 1"` Counter int64 `xorm:"NOT NULL DEFAULT 1"`
CreatedUnix util.TimeStamp `xorm:"created"` CreatedUnix util.TimeStamp `xorm:"created"`
@ -410,6 +411,48 @@ func getOAuth2GrantByID(e Engine, id int64) (grant *OAuth2Grant, err error) {
return 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 // OAuth2TokenType represents the type of token for an oauth application

View file

@ -135,6 +135,25 @@ func TestOAuth2Grant_TableName(t *testing.T) {
assert.Equal(t, "oauth2_grant", new(OAuth2Grant).TableName()) 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 //////////////////// Authorization Code
func TestGetOAuth2AuthorizationByCode(t *testing.T) { func TestGetOAuth2AuthorizationByCode(t *testing.T) {

View file

@ -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_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? 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_desc = Two-factor authentication enhances the security of your account.
twofa_is_enrolled = Your account is currently <strong>enrolled</strong> in two-factor authentication. twofa_is_enrolled = Your account is currently <strong>enrolled</strong> in two-factor authentication.
twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication. twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication.

View file

@ -344,6 +344,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/:id/regenerate_secret", userSetting.OAuthApplicationsRegenerateSecret) m.Post("/:id/regenerate_secret", userSetting.OAuthApplicationsRegenerateSecret)
m.Post("", bindIgnErr(auth.EditOAuth2ApplicationForm{}), userSetting.OAuthApplicationsPost) m.Post("", bindIgnErr(auth.EditOAuth2ApplicationForm{}), userSetting.OAuthApplicationsPost)
m.Post("/delete", userSetting.DeleteOAuth2Application) m.Post("/delete", userSetting.DeleteOAuth2Application)
m.Post("/revoke", userSetting.RevokeOAuth2Grant)
}) })
m.Combo("/applications").Get(userSetting.Applications). m.Combo("/applications").Get(userSetting.Applications).
Post(bindIgnErr(auth.NewAccessTokenForm{}), userSetting.ApplicationsPost) Post(bindIgnErr(auth.NewAccessTokenForm{}), userSetting.ApplicationsPost)

View file

@ -81,5 +81,10 @@ func loadApplicationsData(ctx *context.Context) {
ctx.ServerError("GetOAuth2ApplicationsByUserID", err) ctx.ServerError("GetOAuth2ApplicationsByUserID", err)
return return
} }
ctx.Data["Grants"], err = models.GetOAuth2GrantsByUserID(ctx.User.ID)
if err != nil {
ctx.ServerError("GetOAuth2GrantsByUserID", err)
return
}
} }
} }

View file

@ -5,6 +5,8 @@
package setting package setting
import ( import (
"fmt"
"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/base" "code.gitea.io/gitea/modules/base"
@ -138,3 +140,20 @@ func DeleteOAuth2Application(ctx *context.Context) {
"redirect": setting.AppSubURL + "/user/settings/applications", "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",
})
}

View file

@ -47,6 +47,7 @@
</div> </div>
{{if .EnableOAuth2}} {{if .EnableOAuth2}}
{{template "user/settings/grants_oauth2" .}}
{{template "user/settings/applications_oauth2" .}} {{template "user/settings/applications_oauth2" .}}
{{end}} {{end}}
</div> </div>

View file

@ -0,0 +1,39 @@
<h4 class="ui top attached header">
{{.i18n.Tr "settings.authorized_oauth2_applications"}}
</h4>
<div class="ui attached segment">
<div class="ui key list">
<div class="item">
{{.i18n.Tr "settings.authorized_oauth2_applications_description"}}
</div>
{{range $grant := .Grants}}
<div class="item">
<div class="right floated content">
<button class="ui red tiny button delete-button" id="revoke-gitea-oauth2-grant"
data-url="{{AppSubUrl}}/user/settings/applications/oauth2/revoke"
data-id="{{$grant.ID}}">
{{$.i18n.Tr "settings.revoke_key"}}
</button>
</div>
<i class="big key icon"></i>
<div class="content">
<strong>{{$grant.Application.Name}}</strong>
<div class="activity meta">
<i>{{$.i18n.Tr "settings.add_on"}} <span>{{$grant.CreatedUnix.FormatShort}}</span></i>
</div>
</div>
</div>
{{end}}
</div>
</div>
<div class="ui small basic delete modal" id="revoke-gitea-oauth2-grant">
<div class="ui icon header">
<i class="shield alternate icon"></i>
{{.i18n.Tr "settings.revoke_oauth2_grant"}}
</div>
<div class="content">
<p>{{.i18n.Tr "settings.revoke_oauth2_grant_description"}}</p>
</div>
{{template "base/delete_modal_actions" .}}
</div>