mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-04 22:57:34 -04:00
Merge b58f94acd67968a024be7cd86e428de3576baf6a into 1d4ad5aa2b3a321a8d759bb91fc78e0aa6a89ed9
This commit is contained in:
commit
35492f29d6
@ -240,13 +240,18 @@ func CreateSource(ctx context.Context, source *Source) error {
|
||||
err = registerableSource.RegisterSource()
|
||||
if err != nil {
|
||||
// remove the AuthSource in case of errors while registering configuration
|
||||
if _, err := db.GetEngine(ctx).ID(source.ID).Delete(new(Source)); err != nil {
|
||||
if err := DeleteSource(ctx, source.ID); err != nil {
|
||||
log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteSource(ctx context.Context, id int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(id).Delete(new(Source))
|
||||
return err
|
||||
}
|
||||
|
||||
type FindSourcesOptions struct {
|
||||
db.ListOptions
|
||||
IsActive optional.Option[bool]
|
||||
|
13
modules/structs/auth.go
Normal file
13
modules/structs/auth.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package structs
|
||||
|
||||
type AuthSourceOption struct {
|
||||
ID int64 `json:"id"`
|
||||
AuthenticationName string `json:"authentication_name" binding:"Required"`
|
||||
TypeName string `json:"type_name"`
|
||||
|
||||
IsActive bool `json:"is_active"`
|
||||
IsSyncEnabled bool `json:"is_sync_enabled"`
|
||||
}
|
50
modules/structs/auth_oauth2.go
Normal file
50
modules/structs/auth_oauth2.go
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package structs
|
||||
|
||||
// CreateUserOption create user options
|
||||
type CreateAuthOauth2Option struct {
|
||||
AuthenticationName string `json:"authentication_name" binding:"Required"`
|
||||
ProviderIconURL string `json:"provider_icon_url"`
|
||||
ProviderClientID string `json:"provider_client_id" binding:"Required"`
|
||||
ProviderClientSecret string `json:"provider_client_secret" binding:"Required"`
|
||||
ProviderAutoDiscoveryURL string `json:"provider_auto_discovery_url" binding:"Required"`
|
||||
|
||||
SkipLocal2FA bool `json:"skip_local_2fa"`
|
||||
AdditionalScopes string `json:"additional_scopes"`
|
||||
RequiredClaimName string `json:"required_claim_name"`
|
||||
RequiredClaimValue string `json:"required_claim_value"`
|
||||
|
||||
ClaimNameProvidingGroupNameForSource string `json:"claim_name_providingGroupNameForSource"`
|
||||
GroupClaimValueForAdministratorUsers string `json:"group_claim_value_for_administrator_users"`
|
||||
GroupClaimValueForRestrictedUsers string `json:"group_claim_value_for_restricted_users"`
|
||||
MapClaimedGroupsToOrganizationTeams string `json:"map_claimed_groups_to_organization_teams"`
|
||||
|
||||
RemoveUsersFromSyncronizedTeams bool `json:"RemoveUsersFromSyncronizedTeams"`
|
||||
EnableUserSyncronization bool `json:"EnableUserSyncronization"`
|
||||
AuthenticationSourceIsActive bool `json:"AuthenticationSourceIsActive"`
|
||||
}
|
||||
|
||||
// EditUserOption edit user options
|
||||
type EditAuthOauth2Option struct {
|
||||
AuthenticationName string `json:"authentication_name" binding:"Required"`
|
||||
ProviderIconURL string `json:"provider_icon_url"`
|
||||
ProviderClientID string `json:"provider_client_id" binding:"Required"`
|
||||
ProviderClientSecret string `json:"provider_client_secret" binding:"Required"`
|
||||
ProviderAutoDiscoveryURL string `json:"provider_auto_discovery_url" binding:"Required"`
|
||||
|
||||
SkipLocal2FA bool `json:"skip_local_2fa"`
|
||||
AdditionalScopes string `json:"additional_scopes"`
|
||||
RequiredClaimName string `json:"required_claim_name"`
|
||||
RequiredClaimValue string `json:"required_claim_value"`
|
||||
|
||||
ClaimNameProvidingGroupNameForSource string `json:"claim_name_providingGroupNameForSource"`
|
||||
GroupClaimValueForAdministratorUsers string `json:"group_claim_value_for_administrator_users"`
|
||||
GroupClaimValueForRestrictedUsers string `json:"group_claim_value_for_restricted_users"`
|
||||
MapClaimedGroupsToOrganizationTeams string `json:"map_claimed_groups_to_organization_teams"`
|
||||
|
||||
RemoveUsersFromSyncronizedTeams bool `json:"RemoveUsersFromSyncronizedTeams"`
|
||||
EnableUserSyncronization bool `json:"EnableUserSyncronization"`
|
||||
AuthenticationSourceIsActive bool `json:"AuthenticationSourceIsActive"`
|
||||
}
|
59
routers/api/v1/admin/auth.go
Normal file
59
routers/api/v1/admin/auth.go
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// SearchAuth API for getting information of the configured authentication methods according the filter conditions
|
||||
func SearchAuth(ctx *context.APIContext) {
|
||||
// swagger:operation GET /admin/identity-auth admin adminSearchAuth
|
||||
// ---
|
||||
// summary: Search authentication sources
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// description: "SearchResults of authentication sources"
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/AuthOauth2Option"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
authSources, maxResults, err := db.FindAndCount[auth_model.Source](ctx, auth_model.FindSourcesOptions{})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*api.AuthSourceOption, len(authSources))
|
||||
for i := range authSources {
|
||||
results[i] = convert.ToOauthProvider(ctx, authSources[i])
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &results)
|
||||
}
|
270
routers/api/v1/admin/auth_oauth.go
Normal file
270
routers/api/v1/admin/auth_oauth.go
Normal file
@ -0,0 +1,270 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
// CreateOauthAuth create a new external authentication for oauth2
|
||||
func CreateOauthAuth(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /admin/identity-auth/oauth admin adminCreateOauth2Auth
|
||||
// ---
|
||||
// summary: Create an OAuth2 authentication source
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateAuthOauth2Option"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: OAuth2 authentication source created successfully
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateAuthOauth2Option)
|
||||
|
||||
discoveryURL, err := url.Parse(form.ProviderAutoDiscoveryURL)
|
||||
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
|
||||
_ = fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", form.ProviderAutoDiscoveryURL)
|
||||
ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", form.ProviderAutoDiscoveryURL))
|
||||
}
|
||||
|
||||
config := &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
ClientID: form.ProviderClientID,
|
||||
ClientSecret: form.ProviderClientSecret,
|
||||
OpenIDConnectAutoDiscoveryURL: form.ProviderAutoDiscoveryURL,
|
||||
CustomURLMapping: nil,
|
||||
IconURL: form.ProviderIconURL,
|
||||
Scopes: []string{},
|
||||
RequiredClaimName: form.RequiredClaimName,
|
||||
RequiredClaimValue: form.RequiredClaimValue,
|
||||
SkipLocalTwoFA: form.SkipLocal2FA,
|
||||
|
||||
GroupClaimName: form.ClaimNameProvidingGroupNameForSource,
|
||||
RestrictedGroup: form.GroupClaimValueForRestrictedUsers,
|
||||
AdminGroup: form.GroupClaimValueForAdministratorUsers,
|
||||
GroupTeamMap: form.MapClaimedGroupsToOrganizationTeams,
|
||||
GroupTeamMapRemoval: form.RemoveUsersFromSyncronizedTeams,
|
||||
}
|
||||
|
||||
createErr := auth_model.CreateSource(ctx, &auth_model.Source{
|
||||
Type: auth_model.OAuth2,
|
||||
Name: form.AuthenticationName,
|
||||
IsActive: true,
|
||||
Cfg: config,
|
||||
})
|
||||
|
||||
if createErr != nil {
|
||||
ctx.APIErrorInternal(createErr)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// EditOauthAuth api for modifying a authentication method
|
||||
func EditOauthAuth(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /admin/identity-auth/oauth/{id} admin adminEditOauth2Auth
|
||||
// ---
|
||||
// summary: Update an OAuth2 authentication source
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: authentication source ID
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateAuthOauth2Option"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: OAuth2 authentication source updated successfully
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
oauthIDString := ctx.PathParam("id")
|
||||
oauthID, oauthIDErr := strconv.Atoi(oauthIDString)
|
||||
if oauthIDErr != nil {
|
||||
ctx.APIErrorInternal(oauthIDErr)
|
||||
}
|
||||
|
||||
source, sourceErr := auth_model.GetSourceByID(ctx, int64(oauthID))
|
||||
if sourceErr != nil {
|
||||
ctx.APIErrorInternal(sourceErr)
|
||||
return
|
||||
}
|
||||
|
||||
if source.Type != auth_model.OAuth2 {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateAuthOauth2Option)
|
||||
|
||||
config := &oauth2.Source{
|
||||
Provider: "openidConnect",
|
||||
ClientID: form.ProviderClientID,
|
||||
ClientSecret: form.ProviderClientSecret,
|
||||
OpenIDConnectAutoDiscoveryURL: form.ProviderAutoDiscoveryURL,
|
||||
CustomURLMapping: nil,
|
||||
IconURL: form.ProviderIconURL,
|
||||
Scopes: []string{},
|
||||
RequiredClaimName: form.RequiredClaimName,
|
||||
RequiredClaimValue: form.RequiredClaimValue,
|
||||
SkipLocalTwoFA: form.SkipLocal2FA,
|
||||
|
||||
GroupClaimName: form.ClaimNameProvidingGroupNameForSource,
|
||||
RestrictedGroup: form.GroupClaimValueForRestrictedUsers,
|
||||
AdminGroup: form.GroupClaimValueForAdministratorUsers,
|
||||
GroupTeamMap: form.MapClaimedGroupsToOrganizationTeams,
|
||||
GroupTeamMapRemoval: form.RemoveUsersFromSyncronizedTeams,
|
||||
}
|
||||
|
||||
updateErr := auth_model.UpdateSource(ctx, &auth_model.Source{
|
||||
ID: int64(oauthID),
|
||||
Type: auth_model.OAuth2,
|
||||
Name: form.AuthenticationName,
|
||||
IsActive: true,
|
||||
Cfg: config,
|
||||
})
|
||||
|
||||
if updateErr != nil {
|
||||
ctx.APIErrorInternal(updateErr)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// DeleteOauthAuth api for deleting a authentication method
|
||||
func DeleteOauthAuth(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /admin/identity-auth/oauth/{id} admin adminDeleteOauth2Auth
|
||||
// ---
|
||||
// summary: Delete an OAuth2 authentication source
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: authentication source ID
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// description: OAuth2 authentication source deleted successfully
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
oauthIDString := ctx.PathParam("id")
|
||||
oauthID, oauthIDErr := strconv.Atoi(oauthIDString)
|
||||
if oauthIDErr != nil {
|
||||
ctx.APIErrorInternal(oauthIDErr)
|
||||
}
|
||||
|
||||
source, sourceErr := auth_model.GetSourceByID(ctx, int64(oauthID))
|
||||
if sourceErr != nil {
|
||||
ctx.APIErrorInternal(sourceErr)
|
||||
return
|
||||
}
|
||||
|
||||
if source.Type != auth_model.OAuth2 {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
err := auth_model.DeleteSource(ctx, int64(oauthID))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// SearchOauthAuth API for getting information of the configured authentication methods according the filter conditions
|
||||
func SearchOauthAuth(ctx *context.APIContext) {
|
||||
// swagger:operation GET /admin/identity-auth/oauth admin adminSearchOauth2Auth
|
||||
// ---
|
||||
// summary: Search OAuth2 authentication sources
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// description: "SearchResults of OAuth2 authentication sources"
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/AuthOauth2Option"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
authSources, maxResults, err := db.FindAndCount[auth_model.Source](ctx, auth_model.FindSourcesOptions{
|
||||
LoginType: auth_model.OAuth2,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*api.AuthSourceOption, len(authSources))
|
||||
for i := range authSources {
|
||||
results[i] = convert.ToOauthProvider(ctx, authSources[i])
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &results)
|
||||
}
|
@ -1710,6 +1710,16 @@ func Routes() *web.Router {
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly())
|
||||
|
||||
m.Group("/admin", func() {
|
||||
m.Group("/identity-auth", func() {
|
||||
m.Get("", admin.SearchAuth)
|
||||
m.Group("/oauth", func() {
|
||||
m.Get("", admin.SearchOauthAuth)
|
||||
m.Put("", bind(api.CreateAuthOauth2Option{}), admin.CreateOauthAuth)
|
||||
m.Patch("/{id}", bind(api.EditAuthOauth2Option{}), admin.EditOauthAuth)
|
||||
m.Delete("/{id}", admin.DeleteOauthAuth)
|
||||
})
|
||||
})
|
||||
|
||||
m.Group("/cron", func() {
|
||||
m.Get("", admin.ListCronTasks)
|
||||
m.Post("/{task}", admin.PostCronTask)
|
||||
|
40
services/convert/auth_oauth.go
Normal file
40
services/convert/auth_oauth.go
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
// ToOauthProvider convert auth_model.Source≤ to api.AuthOauth2Option
|
||||
func ToOauthProvider(ctx context.Context, provider *auth_model.Source) *api.AuthSourceOption {
|
||||
if provider == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return toOauthProvider(provider)
|
||||
}
|
||||
|
||||
// ToOauthProviders convert list of auth_model.Source to list of api.AuthOauth2Option
|
||||
func ToOauthProviders(ctx context.Context, provider []*auth_model.Source) []*api.AuthSourceOption {
|
||||
result := make([]*api.AuthSourceOption, len(provider))
|
||||
for i := range provider {
|
||||
result[i] = ToOauthProvider(ctx, provider[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toOauthProvider(provider *auth_model.Source) *api.AuthSourceOption {
|
||||
return &api.AuthSourceOption{
|
||||
ID: provider.ID,
|
||||
AuthenticationName: provider.Name,
|
||||
TypeName: provider.Type.String(),
|
||||
|
||||
IsActive: provider.IsActive,
|
||||
IsSyncEnabled: provider.IsSyncEnabled,
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user