mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-19 09:16:36 +00:00
Allows us to make use of Go features introduced in v1.26. I require a feature from v1.26 for a PR I want to make later. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12369 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
159 lines
4.3 KiB
Go
159 lines
4.3 KiB
Go
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package actions
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"forgejo.org/modules/jwtx"
|
|
"forgejo.org/modules/setting"
|
|
"forgejo.org/modules/web"
|
|
web_types "forgejo.org/modules/web/types"
|
|
actions_service "forgejo.org/services/actions"
|
|
auth_method "forgejo.org/services/auth/method"
|
|
"forgejo.org/services/context"
|
|
)
|
|
|
|
type oidcRoutes struct {
|
|
openIDConfiguration openIDConfiguration
|
|
jwks map[string][]map[string]string
|
|
}
|
|
|
|
type openIDConfiguration struct {
|
|
Issuer string `json:"issuer"`
|
|
JwksURI string `json:"jwks_uri"`
|
|
SubjectTypesSupported []string `json:"subject_types_supported"`
|
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
|
ClaimsSupported []string `json:"claims_supported"`
|
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
|
ScopesSupported []string `json:"scopes_supported"`
|
|
}
|
|
|
|
type oidcContextKeyType struct{}
|
|
|
|
var oidcContextKey = oidcContextKeyType{}
|
|
|
|
// jwtSigningKey is the default signing key for JWTs.
|
|
var jwtSigningKey jwtx.SigningKey
|
|
|
|
// jwk is the JWK format of the jwtSigningKey.
|
|
var jwk map[string]string
|
|
|
|
type OIDCContext struct {
|
|
*context.Base
|
|
}
|
|
|
|
func InitOIDC() error {
|
|
var err error
|
|
jwtSigningKey, err = jwtx.InitSigningKey(&setting.Actions.KeyCfg.Signing)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
jwk, err = jwtSigningKey.ToJWK()
|
|
if err != nil {
|
|
return fmt.Errorf("Error getting JWK from default signing key: %v", err)
|
|
}
|
|
jwk["use"] = "sig"
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
web.RegisterResponseStatusProvider[*OIDCContext](func(req *http.Request) web_types.ResponseStatusProvider {
|
|
return req.Context().Value(oidcContextKey).(*OIDCContext)
|
|
})
|
|
auth_method.RegisterInternalIssuer("api/actions", internalIssuer{})
|
|
}
|
|
|
|
func OIDCContexter() func(next http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
base, baseCleanUp := context.NewBaseContext(resp, req)
|
|
defer baseCleanUp()
|
|
|
|
ctx := &OIDCContext{Base: base}
|
|
ctx.AppendContextValue(oidcContextKey, ctx)
|
|
|
|
next.ServeHTTP(ctx.Resp, ctx.Req)
|
|
})
|
|
}
|
|
}
|
|
|
|
func OIDCRoutes(prefix string) *web.Route {
|
|
m := web.NewRoute()
|
|
|
|
prefix = strings.TrimPrefix(prefix, "/")
|
|
|
|
// Standard claims
|
|
claimsSupported := []string{
|
|
"sub",
|
|
"aud",
|
|
"exp",
|
|
"iat",
|
|
"iss",
|
|
"nbf",
|
|
}
|
|
|
|
// Add custom claims by iterating over [actions_service.IDTokenCustomClaims]
|
|
// and inspecting the names of the json struct tags
|
|
rt := reflect.TypeFor[actions_service.IDTokenCustomClaims]()
|
|
|
|
for f := range rt.Fields() {
|
|
v := strings.Split(f.Tag.Get("json"), ",")[0]
|
|
if v == "" || v == "-" {
|
|
continue
|
|
}
|
|
|
|
claimsSupported = append(claimsSupported, v)
|
|
}
|
|
|
|
o := &oidcRoutes{
|
|
openIDConfiguration: openIDConfiguration{
|
|
Issuer: setting.AppURL + prefix,
|
|
JwksURI: setting.AppURL + prefix + "/.well-known/keys",
|
|
SubjectTypesSupported: []string{"public"},
|
|
ResponseTypesSupported: []string{"id_token"},
|
|
ClaimsSupported: claimsSupported,
|
|
IDTokenSigningAlgValuesSupported: []string{jwtSigningKey.SigningMethod().Alg()},
|
|
ScopesSupported: []string{"openid"},
|
|
},
|
|
jwks: map[string][]map[string]string{
|
|
"keys": {
|
|
jwk,
|
|
},
|
|
},
|
|
}
|
|
|
|
m.Group("", func() {
|
|
m.Get("/keys", o.keys)
|
|
m.Get("/openid-configuration", o.configuration)
|
|
}, OIDCContexter())
|
|
|
|
return m
|
|
}
|
|
|
|
func (o *oidcRoutes) configuration(ctx *OIDCContext) {
|
|
ctx.JSON(http.StatusOK, o.openIDConfiguration)
|
|
}
|
|
|
|
func (o *oidcRoutes) keys(ctx *OIDCContext) {
|
|
ctx.JSON(http.StatusOK, o.jwks)
|
|
}
|
|
|
|
// Register Actions OIDC as an internal issuer for authorized integrations. This allows an authorized integration to
|
|
// have the value `urn:forgejo:authorized-integrations:actions` as an issuer, and perform JWT signature validation
|
|
// against the in-memory signing key defined in this module.
|
|
type internalIssuer struct{}
|
|
|
|
func (internalIssuer) IssuerPlaceholder() string {
|
|
return "urn:forgejo:authorized-integrations:actions"
|
|
}
|
|
|
|
func (internalIssuer) SigningKey() jwtx.SigningKey {
|
|
return jwtSigningKey
|
|
}
|