Files
opencloud/services/idp/pkg/backends/cs3/identifier/cs3.go
2025-01-29 11:58:57 +01:00

231 lines
6.7 KiB
Go

package cs3
import (
"context"
"crypto/tls"
"fmt"
cs3gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/libregraph/lico"
"github.com/libregraph/lico/config"
"github.com/libregraph/lico/identifier/backends"
"github.com/libregraph/lico/identifier/meta/scopes"
"github.com/libregraph/lico/identity"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
cmap "github.com/orcaman/concurrent-map"
"github.com/sirupsen/logrus"
)
const cs3BackendName = "identifier-cs3"
var cs3SpportedScopes = []string{
"profile",
"email",
lico.ScopeUniqueUserID,
lico.ScopeRawSubject,
}
// CS3Backend holds the data for the CS3 identifier backend
type CS3Backend struct {
supportedScopes []string
logger logrus.FieldLogger
tlsConfig *tls.Config
gatewayAddr string
machineAuthAPIKey string
insecure bool
sessions cmap.ConcurrentMap
}
// NewCS3Backend creates a new CS3 backend identifier backend
func NewCS3Backend(
c *config.Config,
tlsConfig *tls.Config,
gatewayAddr string,
machineAuthAPIKey string,
insecure bool,
) (*CS3Backend, error) {
// Build supported scopes based on default scopes.
supportedScopes := make([]string, len(cs3SpportedScopes))
copy(supportedScopes, cs3SpportedScopes)
b := &CS3Backend{
supportedScopes: supportedScopes,
logger: c.Logger,
tlsConfig: tlsConfig,
gatewayAddr: gatewayAddr,
machineAuthAPIKey: machineAuthAPIKey,
insecure: insecure,
sessions: cmap.New(),
}
b.logger.Infoln("cs3 backend connection set up")
return b, nil
}
// RunWithContext implements the Backend interface.
func (b *CS3Backend) RunWithContext(_ context.Context) error {
return nil
}
// Logon implements the Backend interface, enabling Logon with user name and
// password as provided. Requests are bound to the provided context.
func (b *CS3Backend) Logon(ctx context.Context, audience, username, password string) (bool, *string, *string, backends.UserFromBackend, error) {
client, err := pool.GetGatewayServiceClient(b.gatewayAddr)
if err != nil {
return false, nil, nil, nil, err
}
res, err := client.Authenticate(ctx, &cs3gateway.AuthenticateRequest{
Type: "basic",
ClientId: username,
ClientSecret: password,
})
if err != nil {
return false, nil, nil, nil, fmt.Errorf("cs3 backend basic authenticate rpc error: %v", err)
}
if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
return false, nil, nil, nil, fmt.Errorf("cs3 backend basic authenticate failed with code %s: %s", res.GetStatus().GetCode().String(), res.GetStatus().GetMessage())
}
session := createSession(res.GetUser())
user, err := newCS3User(res.GetUser())
if err != nil {
return false, nil, nil, nil, fmt.Errorf("cs3 backend resolve entry data error: %v", err)
}
// Use the users subject as user id.
userID := user.Subject()
sessionRef := identity.GetSessionRef(b.Name(), audience, userID)
b.sessions.Set(*sessionRef, session)
b.logger.WithFields(logrus.Fields{
"session": session,
"ref": *sessionRef,
"username": user.Username(),
"id": userID,
}).Debugln("cs3 backend logon")
return true, &userID, sessionRef, user, nil
}
// GetUser implements the Backend interface, providing user meta data retrieval
// for the user specified by the userID. Requests are bound to the provided
// context.
func (b *CS3Backend) GetUser(ctx context.Context, userEntryID string, sessionRef *string, _ map[string]bool) (backends.UserFromBackend, error) {
var session *cs3Session
if s, ok := b.sessions.Get(*sessionRef); ok {
// We have a cached session
session = s.(*cs3Session)
if session != nil {
user, err := newCS3User(session.User())
if err != nil {
return nil, fmt.Errorf("cs3 backend get user failed to process user: %v", err)
}
return user, nil
}
}
// rebuild session
client, err := pool.GetGatewayServiceClient(b.gatewayAddr)
if err != nil {
return nil, err
}
res, err := client.Authenticate(ctx, &cs3gateway.AuthenticateRequest{
Type: "machine",
ClientId: "userid:" + userEntryID,
ClientSecret: b.machineAuthAPIKey,
})
if err != nil {
return nil, fmt.Errorf("cs3 backend get user machine authenticate rpc error: %v", err)
}
if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
return nil, fmt.Errorf("cs3 backend get user machine authenticate failed with code %s: %s", res.GetStatus().GetCode().String(), res.GetStatus().GetMessage())
}
// cache session
session = createSession(res.GetUser())
b.sessions.Set(*sessionRef, session)
user, err := newCS3User(res.GetUser())
if err != nil {
return nil, fmt.Errorf("cs3 backend get user data error: %v", err)
}
return user, nil
}
// ResolveUserByUsername implements the Backend interface, providing lookup for
// user by providing the username. Requests are bound to the provided context.
func (b *CS3Backend) ResolveUserByUsername(ctx context.Context, username string) (backends.UserFromBackend, error) {
client, err := pool.GetGatewayServiceClient(b.gatewayAddr)
if err != nil {
return nil, err
}
res, err := client.Authenticate(ctx, &cs3gateway.AuthenticateRequest{
Type: "machine",
ClientId: "username:" + username,
ClientSecret: b.machineAuthAPIKey,
})
if err != nil {
return nil, fmt.Errorf("cs3 backend machine authenticate rpc error: %v", err)
}
if res.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
return nil, fmt.Errorf("cs3 backend machine authenticate failed with code %s: %s", res.GetStatus().GetCode().String(), res.GetStatus().GetMessage())
}
user, err := newCS3User(res.GetUser())
if err != nil {
return nil, fmt.Errorf("cs3 backend resolve username data error: %v", err)
}
return user, nil
}
// RefreshSession implements the Backend interface.
func (b *CS3Backend) RefreshSession(_ context.Context, _ string, _ *string, _ map[string]interface{}) error {
return nil
}
// DestroySession implements the Backend interface providing destroy CS3 session.
func (b *CS3Backend) DestroySession(_ context.Context, sessionRef *string) error {
b.sessions.Remove(*sessionRef)
return nil
}
// UserClaims implements the Backend interface, providing user specific claims
// for the user specified by the userID.
func (b *CS3Backend) UserClaims(_ string, _ map[string]bool) map[string]interface{} {
return nil
}
// ScopesSupported implements the Backend interface, providing supported scopes
// when running this backend.
func (b *CS3Backend) ScopesSupported() []string {
return b.supportedScopes
}
// ScopesMeta implements the Backend interface, providing meta data for
// supported scopes.
func (b *CS3Backend) ScopesMeta() *scopes.Scopes {
return nil
}
// Name implements the Backend interface.
func (b *CS3Backend) Name() string {
return cs3BackendName
}