Allowed client specific perms

This commit is contained in:
Melon 2023-10-26 11:30:04 +01:00
parent 1940f2de71
commit 6f16ea6690
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
4 changed files with 56 additions and 29 deletions

View File

@ -11,6 +11,10 @@ type Conf struct {
ServiceName string `json:"service_name"` ServiceName string `json:"service_name"`
Issuer string `json:"issuer"` Issuer string `json:"issuer"`
SsoServices []issuer.SsoConfig `json:"sso_services"` SsoServices []issuer.SsoConfig `json:"sso_services"`
AllowedClients []utils.JsonUrl `json:"allowed_clients"` AllowedClients []AllowedClient `json:"allowed_clients"`
Permissions []string `json:"permissions"` }
type AllowedClient struct {
Url utils.JsonUrl `json:"url"`
Permissions []string `json:"permissions"`
} }

View File

@ -51,7 +51,8 @@ func (h *HttpServer) flowPopupPost(rw http.ResponseWriter, req *http.Request, _
loginUn := loginName[:n] loginUn := loginName[:n]
targetOrigin := req.PostFormValue("origin") targetOrigin := req.PostFormValue("origin")
if _, found := h.services[targetOrigin]; !found { allowedService, found := h.services[targetOrigin]
if !found {
http.Error(rw, "Invalid target origin", http.StatusBadRequest) http.Error(rw, "Invalid target origin", http.StatusBadRequest)
return return
} }
@ -60,7 +61,7 @@ func (h *HttpServer) flowPopupPost(rw http.ResponseWriter, req *http.Request, _
state := login.Config.Namespace + ":" + uuidNewStringState() state := login.Config.Namespace + ":" + uuidNewStringState()
h.flowState.Set(state, flowStateData{ h.flowState.Set(state, flowStateData{
login, login,
targetOrigin, allowedService,
}, time.Now().Add(15*time.Minute)) }, time.Now().Add(15*time.Minute))
// generate oauth2 config and redirect to authorize URL // generate oauth2 config and redirect to authorize URL
@ -128,26 +129,36 @@ func (h *HttpServer) flowCallback(rw http.ResponseWriter, req *http.Request, _ h
return return
} }
var needsMailFlag bool
ps := claims.NewPermStorage() ps := claims.NewPermStorage()
for _, i := range h.conf.Permissions { for _, i := range v.target.Permissions {
ps.Set(i) if strings.HasPrefix(i, "dynamic:") {
if i == "dynamic:mail-client" {
needsMailFlag = true
}
} else {
ps.Set(i)
}
} }
if verified, ok := v3["email_verified"].(bool); ok && verified { if needsMailFlag {
if mailAddress, ok := v3["email"].(string); ok { if verified, ok := v3["email_verified"].(bool); ok && verified {
address, err := mail.ParseAddress(mailAddress) if mailAddress, ok := v3["email"].(string); ok {
if err != nil { address, err := mail.ParseAddress(mailAddress)
http.Error(rw, "Invalid email in userinfo", http.StatusInternalServerError) if err != nil {
return http.Error(rw, "Invalid email in userinfo", http.StatusInternalServerError)
return
}
n := strings.IndexByte(address.Address, '@')
if n == -1 {
goto noEmailSupport
}
if address.Address[n+1:] != v.sso.Config.Namespace {
goto noEmailSupport
}
ps.Set("mail-client")
} }
n := strings.IndexByte(address.Address, '@')
if n == -1 {
goto noEmailSupport
}
if address.Address[n+1:] != v.sso.Config.Namespace {
goto noEmailSupport
}
ps.Set("mail-client")
} }
} }
@ -170,7 +181,7 @@ noEmailSupport:
pages.RenderPageTemplate(rw, "flow-callback", map[string]any{ pages.RenderPageTemplate(rw, "flow-callback", map[string]any{
"ServiceName": h.conf.ServiceName, "ServiceName": h.conf.ServiceName,
"TargetOrigin": v.targetOrigin, "TargetOrigin": v.target.Url.String(),
"TargetMessage": v3, "TargetMessage": v3,
"AccessToken": accessToken, "AccessToken": accessToken,
"RefreshToken": refreshToken, "RefreshToken": refreshToken,

View File

@ -29,6 +29,8 @@ const lavenderDomain = "http://localhost:0"
const clientAppDomain = "http://localhost:1" const clientAppDomain = "http://localhost:1"
const loginDomain = "http://localhost:2" const loginDomain = "http://localhost:2"
var clientAppMeta AllowedClient
var testSigner mjwt.Signer var testSigner mjwt.Signer
var testOidc = &issuer.WellKnownOIDC{ var testOidc = &issuer.WellKnownOIDC{
@ -70,7 +72,7 @@ var testHttpServer = HttpServer{
}, },
manager: testManager, manager: testManager,
flowState: cache.New[string, flowStateData](), flowState: cache.New[string, flowStateData](),
services: map[string]struct{}{ services: map[string]AllowedClient{
clientAppDomain: {}, clientAppDomain: {},
}, },
} }
@ -88,6 +90,16 @@ func init() {
testSigner = mjwt.NewMJwtSigner("https://example.com", key) testSigner = mjwt.NewMJwtSigner("https://example.com", key)
testHttpServer.signer = testSigner testHttpServer.signer = testSigner
parse, err := url.Parse(clientAppDomain)
if err != nil {
panic(err)
}
clientAppMeta = AllowedClient{
Url: utils.JsonUrl{URL: parse},
Permissions: []string{"test-perm"},
}
} }
func TestFlowPopup(t *testing.T) { func TestFlowPopup(t *testing.T) {
@ -168,8 +180,8 @@ func TestFlowCallback(t *testing.T) {
expiryTime := time.Now().Add(15 * time.Minute) expiryTime := time.Now().Add(15 * time.Minute)
nextState := uuid.NewString() nextState := uuid.NewString()
testHttpServer.flowState.Set("example.com:"+nextState, flowStateData{ testHttpServer.flowState.Set("example.com:"+nextState, flowStateData{
sso: testOidc, sso: testOidc,
targetOrigin: clientAppDomain, target: clientAppMeta,
}, expiryTime) }, expiryTime)
testOa2Exchange = func(oa2conf oauth2.Config, ctx context.Context, code string) (*oauth2.Token, error) { testOa2Exchange = func(oa2conf oauth2.Config, ctx context.Context, code string) (*oauth2.Token, error) {

View File

@ -17,12 +17,12 @@ type HttpServer struct {
manager *issuer.Manager manager *issuer.Manager
signer mjwt.Signer signer mjwt.Signer
flowState *cache.Cache[string, flowStateData] flowState *cache.Cache[string, flowStateData]
services map[string]struct{} services map[string]AllowedClient
} }
type flowStateData struct { type flowStateData struct {
sso *issuer.WellKnownOIDC sso *issuer.WellKnownOIDC
targetOrigin string target AllowedClient
} }
func NewHttpServer(conf Conf, signer mjwt.Signer) *http.Server { func NewHttpServer(conf Conf, signer mjwt.Signer) *http.Server {
@ -41,9 +41,9 @@ func NewHttpServer(conf Conf, signer mjwt.Signer) *http.Server {
log.Fatal("[Lavender] Failed to create SSO service manager: ", err) log.Fatal("[Lavender] Failed to create SSO service manager: ", err)
} }
services := make(map[string]struct{}) services := make(map[string]AllowedClient)
for _, i := range conf.AllowedClients { for _, i := range conf.AllowedClients {
services[i.String()] = struct{}{} services[i.Url.String()] = i
} }
hs := &HttpServer{ hs := &HttpServer{