* 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>tags/v1.9.0-rc1
@@ -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 |
@@ -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) { |
@@ -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 <strong>enrolled</strong> in two-factor authentication. | |||
twofa_not_enrolled = Your account is not currently enrolled in two-factor authentication. |
@@ -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) |
@@ -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 | |||
} | |||
} | |||
} |
@@ -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", | |||
}) | |||
} |
@@ -47,6 +47,7 @@ | |||
</div> | |||
{{if .EnableOAuth2}} | |||
{{template "user/settings/grants_oauth2" .}} | |||
{{template "user/settings/applications_oauth2" .}} | |||
{{end}} | |||
</div> |
@@ -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> |