mirror of
https://github.com/wso2/open-mcp-auth-proxy.git
synced 2025-07-21 17:59:38 +00:00
Merge 1964829dcd
into 56cdc96cb6
This commit is contained in:
commit
5e1ea3aaf1
15 changed files with 609 additions and 100 deletions
24
internal/authz/access_control.go
Normal file
24
internal/authz/access_control.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package authz
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/wso2/open-mcp-auth-proxy/internal/config"
|
||||
)
|
||||
|
||||
type Decision int
|
||||
|
||||
const (
|
||||
DecisionAllow Decision = iota
|
||||
DecisionDeny
|
||||
)
|
||||
|
||||
type AccessControlResult struct {
|
||||
Decision Decision
|
||||
Message string
|
||||
}
|
||||
|
||||
type AccessControl interface {
|
||||
ValidateAccess(r *http.Request, claims *jwt.MapClaims, config *config.Config) AccessControlResult
|
||||
}
|
|
@ -350,3 +350,23 @@ func randomString(n int) string {
|
|||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (p *asgardeoProvider) ProtectedResourceMetadataHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
meta := map[string]interface{}{
|
||||
"resource": p.cfg.ResourceIdentifier,
|
||||
"scopes_supported": p.cfg.ScopesSupported,
|
||||
"authorization_servers": p.cfg.AuthorizationServers,
|
||||
}
|
||||
if p.cfg.JwksURI != "" {
|
||||
meta["jwks_uri"] = p.cfg.JwksURI
|
||||
}
|
||||
if len(p.cfg.BearerMethodsSupported) > 0 {
|
||||
meta["bearer_methods_supported"] = p.cfg.BearerMethodsSupported
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(meta); err != nil {
|
||||
http.Error(w, "failed to encode metadata", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,3 +94,27 @@ func (p *defaultProvider) WellKnownHandler() http.HandlerFunc {
|
|||
func (p *defaultProvider) RegisterHandler() http.HandlerFunc {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *defaultProvider) ProtectedResourceMetadataHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
meta := map[string]interface{}{
|
||||
"audience": p.cfg.Audience,
|
||||
"resource": p.cfg.ResourceIdentifier,
|
||||
"scopes_supported": p.cfg.ScopesSupported,
|
||||
"authorization_servers": p.cfg.AuthorizationServers,
|
||||
}
|
||||
|
||||
if p.cfg.JwksURI != "" {
|
||||
meta["jwks_uri"] = p.cfg.JwksURI
|
||||
}
|
||||
|
||||
if len(p.cfg.BearerMethodsSupported) > 0 {
|
||||
meta["bearer_methods_supported"] = p.cfg.BearerMethodsSupported
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(meta); err != nil {
|
||||
http.Error(w, "failed to encode metadata", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,5 @@ import "net/http"
|
|||
type Provider interface {
|
||||
WellKnownHandler() http.HandlerFunc
|
||||
RegisterHandler() http.HandlerFunc
|
||||
ProtectedResourceMetadataHandler() http.HandlerFunc
|
||||
}
|
||||
|
|
71
internal/authz/scope_validator.go
Normal file
71
internal/authz/scope_validator.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package authz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/wso2/open-mcp-auth-proxy/internal/config"
|
||||
"github.com/wso2/open-mcp-auth-proxy/internal/util"
|
||||
)
|
||||
|
||||
type ScopeValidator struct{}
|
||||
|
||||
// Evaluate and checks the token claims against one or more required scopes.
|
||||
func (d *ScopeValidator) ValidateAccess(
|
||||
r *http.Request,
|
||||
claims *jwt.MapClaims,
|
||||
config *config.Config,
|
||||
) AccessControlResult {
|
||||
env, err := util.ParseRPCRequest(r)
|
||||
if err != nil {
|
||||
return AccessControlResult{DecisionDeny, "bad JSON-RPC request"}
|
||||
}
|
||||
requiredScopes := util.GetRequiredScopes(config, env.Method)
|
||||
if len(requiredScopes) == 0 {
|
||||
return AccessControlResult{DecisionAllow, ""}
|
||||
}
|
||||
|
||||
required := make(map[string]struct{}, len(requiredScopes))
|
||||
for _, s := range requiredScopes {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
required[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var tokenScopes []string
|
||||
if claims, ok := (*claims)["scope"]; ok {
|
||||
switch v := claims.(type) {
|
||||
case string:
|
||||
tokenScopes = strings.Fields(v)
|
||||
case []interface{}:
|
||||
for _, x := range v {
|
||||
if s, ok := x.(string); ok && s != "" {
|
||||
tokenScopes = append(tokenScopes, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokenScopeSet := make(map[string]struct{}, len(tokenScopes))
|
||||
for _, s := range tokenScopes {
|
||||
tokenScopeSet[s] = struct{}{}
|
||||
}
|
||||
|
||||
var missing []string
|
||||
for s := range required {
|
||||
if _, ok := tokenScopeSet[s]; !ok {
|
||||
missing = append(missing, s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) == 0 {
|
||||
return AccessControlResult{DecisionAllow, ""}
|
||||
}
|
||||
return AccessControlResult{
|
||||
DecisionDeny,
|
||||
fmt.Sprintf("missing required scope(s): %s", strings.Join(missing, ", ")),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue