Instead of showing the signup form and returning an internal error on submit, the SignUpPage now queries signUpEnabled upfront and displays a friendly message explaining that registration is not available, with a link back to login. Adds a signUpEnabled GraphQL query field on the connect/v1 API and handles ErrSignupDisabled as a FORBIDDEN error in the SignUp resolver. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
2123 lines
71 KiB
Go
2123 lines
71 KiB
Go
package connect_v1
|
|
|
|
// This file will be automatically regenerated based on the schema, any resolver
|
|
// implementations
|
|
// will be copied through when generating and any unknown code will be moved to the end.
|
|
// Code generated by github.com/99designs/gqlgen version v0.17.87
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/99designs/gqlgen/graphql"
|
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
|
"go.gearno.de/kit/log"
|
|
"go.probo.inc/probo/pkg/coredata"
|
|
"go.probo.inc/probo/pkg/gid"
|
|
"go.probo.inc/probo/pkg/iam"
|
|
"go.probo.inc/probo/pkg/iam/scim/bridge/provider/googleworkspace"
|
|
"go.probo.inc/probo/pkg/mail"
|
|
"go.probo.inc/probo/pkg/page"
|
|
"go.probo.inc/probo/pkg/server/api/authn"
|
|
"go.probo.inc/probo/pkg/server/api/authz"
|
|
"go.probo.inc/probo/pkg/server/api/connect/v1/schema"
|
|
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
|
|
"go.probo.inc/probo/pkg/server/gqlutils"
|
|
"go.probo.inc/probo/pkg/server/gqlutils/types/cursor"
|
|
)
|
|
|
|
// Organization is the resolver for the organization field.
|
|
func (r *auditLogEntryResolver) Organization(ctx context.Context, obj *types.AuditLogEntry) (*types.Organization, error) {
|
|
return obj.Organization, nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *auditLogEntryResolver) Permission(ctx context.Context, obj *types.AuditLogEntry, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *auditLogEntryConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditLogEntryConnection) (int, error) {
|
|
filter := coredata.NewAuditLogEntryFilter()
|
|
if obj.Filter != nil {
|
|
filter = obj.Filter
|
|
}
|
|
|
|
count, err := r.iam.OrganizationService.CountAuditLogEntries(ctx, obj.ParentID, filter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count audit log entries", log.Error(err))
|
|
return 0, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *connectorResolver) Permission(ctx context.Context, obj *types.Connector, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// Profiles is the resolver for the profiles field.
|
|
func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy, filter *types.ProfileFilter) (*types.ProfileConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList, authz.WithSkipAssumptionCheck()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filters := coredata.NewMembershipProfileFilter(nil).WithMembership()
|
|
if filter != nil {
|
|
filters = coredata.NewMembershipProfileFilter(filter.ExcludeContractEnded).WithMembership()
|
|
if filter.State != nil {
|
|
filters.WithState(*filter.State)
|
|
}
|
|
}
|
|
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.ProfileConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
Filters: filters,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
|
|
Field: coredata.MembershipProfileOrderFieldFullName,
|
|
Direction: page.OrderDirectionAsc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListProfilesForIdentity(ctx, obj.ID, cursor, filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewProfileConnection(page, r, obj.ID, filters), nil
|
|
}
|
|
|
|
// Sessions is the resolver for the sessions field.
|
|
func (r *identityResolver) Sessions(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SessionOrder) (*types.SessionConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionSessionList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.SessionConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.SessionOrderField]{
|
|
Field: coredata.SessionOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.SessionOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListSessions(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list sessions", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewSessionConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// PersonalAPIKeys is the resolver for the personalAPIKeys field.
|
|
func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Identity, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.PersonalAPIKeyConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.PersonalAPIKeyConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.PersonalAPIKeyOrderField]{
|
|
Field: coredata.PersonalAPIKeyOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListPersonalAPIKeys(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list personal api keys", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewPersonalAPIKeyConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// SsoLoginURL is the resolver for the ssoLoginURL field.
|
|
func (r *identityResolver) SsoLoginURL(ctx context.Context, obj *types.Identity) (*string, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionIdentityGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, identity.EmailAddress)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
if count != 1 {
|
|
if count == 0 {
|
|
return nil, graphql.ErrorOnPath(
|
|
ctx,
|
|
fmt.Errorf("no SAML configuration for email"),
|
|
)
|
|
}
|
|
|
|
return nil, graphql.ErrorOnPath(
|
|
ctx,
|
|
fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"),
|
|
)
|
|
}
|
|
|
|
samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, identity.EmailAddress)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
if len(samlConfigs) == 0 {
|
|
r.logger.ErrorCtx(ctx, "cannot find SAML config")
|
|
return nil, gqlutils.NotFoundf(ctx, "cannot find SAML config")
|
|
}
|
|
samlConfig := samlConfigs[0]
|
|
|
|
loginURL := r.SSOLoginURL(samlConfig.ID)
|
|
return &loginURL, nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *identityResolver) Permission(ctx context.Context, obj *types.Identity, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *invitationResolver) Permission(ctx context.Context, obj *types.Invitation, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// LastSession is the resolver for the lastSession field.
|
|
func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
session := authn.SessionFromContext(ctx)
|
|
if session == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
childSession, err := r.iam.SessionService.GetActiveSessionForMembership(ctx, session.ID, obj.ID)
|
|
if err != nil {
|
|
var errSessionNotFound *iam.ErrSessionNotFound
|
|
if errors.As(err, &errSessionNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get active session for membership", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewSession(childSession), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *membershipResolver) Permission(ctx context.Context, obj *types.Membership, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// SignIn is the resolver for the signIn field.
|
|
func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) {
|
|
identity, err := r.iam.AuthService.CheckCredentials(ctx, input.Email, input.Password)
|
|
if err != nil {
|
|
var errInvalidPassword *iam.ErrInvalidPassword
|
|
if errors.As(err, &errInvalidPassword) {
|
|
return nil, gqlutils.Invalid(ctx, err)
|
|
}
|
|
|
|
var errInvalidCredentials *iam.ErrInvalidCredentials
|
|
if errors.As(err, &errInvalidCredentials) {
|
|
return nil, &gqlerror.Error{
|
|
Message: err.Error(),
|
|
Extensions: map[string]any{
|
|
"code": "INVALID_CREDENTIALS",
|
|
},
|
|
}
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot check credentials", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
session := authn.SessionFromContext(ctx)
|
|
|
|
switch {
|
|
case session == nil:
|
|
var err error
|
|
session, err = r.iam.AuthService.OpenSessionWithPassword(
|
|
ctx,
|
|
identity.ID,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
case session.IdentityID != identity.ID:
|
|
if err := r.iam.SessionService.CloseSession(ctx, session.ID); err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
session, err = r.iam.AuthService.OpenSessionWithPassword(
|
|
ctx,
|
|
identity.ID,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot create session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
}
|
|
|
|
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
|
r.sessionCookie.Set(w, session)
|
|
|
|
if input.OrganizationID != nil {
|
|
var err error
|
|
_, _, err = r.iam.SessionService.OpenPasswordChildSessionForOrganization(ctx, session.ID, *input.OrganizationID)
|
|
if err != nil {
|
|
// Here session middleware already took care of expired/nil root session so we only handle membership related errors
|
|
var errMembershipNotFound *iam.ErrMembershipNotFound
|
|
var errUserInactive *iam.ErrUserInactive
|
|
|
|
if errors.As(err, &errMembershipNotFound) || errors.As(err, &errUserInactive) {
|
|
return nil, gqlutils.Forbiddenf(ctx, "forbidden")
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot assume organization", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
}
|
|
|
|
return &types.SignInPayload{
|
|
Identity: types.NewIdentity(identity),
|
|
Session: types.NewSession(session),
|
|
}, nil
|
|
}
|
|
|
|
// SignUp is the resolver for the signUp field.
|
|
func (r *mutationResolver) SignUp(ctx context.Context, input types.SignUpInput) (*types.SignUpPayload, error) {
|
|
identity, session, err := r.iam.AuthService.CreateIdentityWithPassword(
|
|
ctx,
|
|
&iam.CreateIdentityWithPasswordRequest{
|
|
Email: input.Email,
|
|
Password: input.Password,
|
|
FullName: input.FullName,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var errIdentityAlreadyExists *iam.ErrIdentityAlreadyExists
|
|
if errors.As(err, &errIdentityAlreadyExists) {
|
|
return nil, gqlutils.Invalid(ctx, err)
|
|
}
|
|
|
|
var errSignupDisabled *iam.ErrSignupDisabled
|
|
if errors.As(err, &errSignupDisabled) {
|
|
return nil, gqlutils.Forbidden(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create identity with password", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
|
r.sessionCookie.Set(w, session)
|
|
|
|
return &types.SignUpPayload{
|
|
Identity: types.NewIdentity(identity),
|
|
}, nil
|
|
}
|
|
|
|
// SignOut is the resolver for the signOut field.
|
|
func (r *mutationResolver) SignOut(ctx context.Context) (*types.SignOutPayload, error) {
|
|
session := authn.SessionFromContext(ctx)
|
|
|
|
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
|
if err != nil {
|
|
var ErrSessionNotFound *iam.ErrSessionNotFound
|
|
if errors.As(err, &ErrSessionNotFound) {
|
|
return &types.SignOutPayload{}, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
|
r.sessionCookie.Clear(w)
|
|
|
|
return &types.SignOutPayload{Success: true}, nil
|
|
}
|
|
|
|
// ActivateAccount is the resolver for the activateAccount field.
|
|
func (r *mutationResolver) ActivateAccount(ctx context.Context, input types.ActivateAccountInput) (*types.ActivateAccountPayload, error) {
|
|
session := authn.SessionFromContext(ctx)
|
|
|
|
if session != nil {
|
|
// Sign out any other account before activating a new one
|
|
err := r.iam.SessionService.CloseSession(ctx, session.ID)
|
|
if err != nil {
|
|
var ErrSessionNotFound *iam.ErrSessionNotFound
|
|
if !errors.As(err, &ErrSessionNotFound) {
|
|
r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
}
|
|
|
|
w := gqlutils.HTTPResponseWriterFromContext(ctx)
|
|
r.sessionCookie.Clear(w)
|
|
}
|
|
|
|
identity, user, err := r.iam.AuthService.ActivateAccount(
|
|
ctx,
|
|
&iam.ActivateAccountRequest{
|
|
InvitationToken: input.Token,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var (
|
|
errInvalidToken *iam.ErrInvalidToken
|
|
errInvitationNotFound *iam.ErrInvitationNotFound
|
|
errInvitationExpired *iam.ErrInvitationExpired
|
|
|
|
isInvalidErr = errors.As(err, &errInvalidToken) ||
|
|
errors.As(err, &errInvitationNotFound) ||
|
|
errors.As(err, &errInvitationExpired)
|
|
)
|
|
|
|
if isInvalidErr {
|
|
return nil, gqlutils.Invalid(ctx, err)
|
|
}
|
|
|
|
if _, ok := errors.AsType[*iam.ErrInvitationAlreadyAccepted](err); ok {
|
|
return nil, gqlutils.AccountAlreadyActivated(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot activate account from invitation", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
var ssoLoginURL *string
|
|
samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, user.EmailAddress)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
for _, samlConfig := range samlConfigs {
|
|
if samlConfig.OrganizationID != user.OrganizationID {
|
|
continue
|
|
}
|
|
|
|
ssoLoginURL = new(r.SSOLoginURL(samlConfig.ID))
|
|
}
|
|
|
|
if ssoLoginURL != nil {
|
|
return &types.ActivateAccountPayload{
|
|
CreatePasswordToken: nil,
|
|
SsoLoginURL: ssoLoginURL,
|
|
Profile: types.NewProfile(user),
|
|
}, nil
|
|
}
|
|
|
|
var createPasswordToken *string
|
|
if identity.HashedPassword == nil {
|
|
token, err := r.iam.AuthService.GetResetPasswordToken(ctx, identity.EmailAddress)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot generate password create token", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
createPasswordToken = &token
|
|
}
|
|
|
|
return &types.ActivateAccountPayload{
|
|
CreatePasswordToken: createPasswordToken,
|
|
SsoLoginURL: nil,
|
|
Profile: types.NewProfile(user),
|
|
}, nil
|
|
}
|
|
|
|
// ForgotPassword is the resolver for the forgotPassword field.
|
|
func (r *mutationResolver) ForgotPassword(ctx context.Context, input types.ForgotPasswordInput) (*types.ForgotPasswordPayload, error) {
|
|
err := r.iam.AuthService.SendPasswordResetInstructionByEmail(
|
|
ctx,
|
|
input.Email,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot send password reset instruction by email", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ForgotPasswordPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// ResetPassword is the resolver for the resetPassword field.
|
|
func (r *mutationResolver) ResetPassword(ctx context.Context, input types.ResetPasswordInput) (*types.ResetPasswordPayload, error) {
|
|
err := r.iam.AuthService.ResetPassword(
|
|
ctx,
|
|
&iam.ResetPasswordRequest{
|
|
Token: input.Token,
|
|
Password: input.Password,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var errInvalidToken *iam.ErrInvalidToken
|
|
if errors.As(err, &errInvalidToken) {
|
|
return nil, gqlutils.Invalid(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot reset password", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ResetPasswordPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// VerifyEmail is the resolver for the verifyEmail field.
|
|
func (r *mutationResolver) VerifyEmail(ctx context.Context, input types.VerifyEmailInput) (*types.VerifyEmailPayload, error) {
|
|
err := r.iam.AccountService.VerifyEmail(ctx, input.Token)
|
|
if err != nil {
|
|
var (
|
|
errInvalidToken *iam.ErrInvalidToken
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
errEmailAlreadyVerified *iam.ErrEmailAlreadyVerified
|
|
errEmailVerificationMismatch *iam.ErrEmailVerificationMismatch
|
|
|
|
isInvalidErr = errors.As(err, &errInvalidToken) ||
|
|
errors.As(err, &errEmailVerificationMismatch)
|
|
)
|
|
|
|
if isInvalidErr {
|
|
return nil, gqlutils.Invalid(ctx, err)
|
|
}
|
|
|
|
if errors.As(err, &errEmailAlreadyVerified) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
if errors.As(err, &errIdentityNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot verify email", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.VerifyEmailPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// ChangePassword is the resolver for the changePassword field.
|
|
func (r *mutationResolver) ChangePassword(ctx context.Context, input types.ChangePasswordInput) (*types.ChangePasswordPayload, error) {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
err := r.iam.AccountService.ChangePassword(
|
|
ctx,
|
|
identity.ID,
|
|
&iam.ChangePasswordRequest{
|
|
CurrentPassword: input.CurrentPassword,
|
|
NewPassword: input.NewPassword,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var (
|
|
errInvalidPassword *iam.ErrInvalidPassword
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
)
|
|
|
|
if errors.As(err, &errInvalidPassword) {
|
|
return nil, gqlutils.Invalid(ctx, err)
|
|
}
|
|
|
|
if errors.As(err, &errIdentityNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot change password", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ChangePasswordPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// ChangeEmail is the resolver for the changeEmail field.
|
|
func (r *mutationResolver) ChangeEmail(ctx context.Context, input types.ChangeEmailInput) (*types.ChangeEmailPayload, error) {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
err := r.iam.AccountService.ChangeEmail(
|
|
ctx,
|
|
identity.ID,
|
|
&iam.ChangeEmailRequest{
|
|
NewEmail: input.NewEmail,
|
|
Password: input.Password,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var (
|
|
errInvalidPassword *iam.ErrInvalidPassword
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
)
|
|
|
|
if errors.As(err, &errInvalidPassword) {
|
|
return nil, gqlutils.Invalid(ctx, err)
|
|
}
|
|
|
|
if errors.As(err, &errIdentityNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot change email", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.ChangeEmailPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// AssumeOrganizationSession is the resolver for the assumeOrganizationSession field.
|
|
func (r *mutationResolver) AssumeOrganizationSession(ctx context.Context, input types.AssumeOrganizationSessionInput) (*types.AssumeOrganizationSessionPayload, error) {
|
|
rootSession := authn.SessionFromContext(ctx)
|
|
|
|
childSession, membership, err := r.iam.SessionService.AssumeOrganizationSession(ctx, rootSession.ID, input.OrganizationID, input.Continue)
|
|
if err != nil {
|
|
var (
|
|
errMembershipNotFound *iam.ErrMembershipNotFound
|
|
errPasswordAuthenticationRequired *iam.ErrPasswordAuthenticationRequired
|
|
errSAMLAuthenticationRequired *iam.ErrSAMLAuthenticationRequired
|
|
)
|
|
|
|
switch {
|
|
case errors.As(err, &errMembershipNotFound):
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
|
|
case errors.As(err, &errPasswordAuthenticationRequired):
|
|
return &types.AssumeOrganizationSessionPayload{
|
|
Result: types.PasswordRequired{
|
|
Reason: types.ReauthenticationReason(errPasswordAuthenticationRequired.Reason),
|
|
},
|
|
}, nil
|
|
|
|
case errors.As(err, &errSAMLAuthenticationRequired):
|
|
return &types.AssumeOrganizationSessionPayload{
|
|
Result: types.SAMLAuthenticationRequired{
|
|
Reason: types.ReauthenticationReason(errSAMLAuthenticationRequired.Reason),
|
|
},
|
|
}, nil
|
|
|
|
default:
|
|
r.logger.ErrorCtx(ctx, "cannot assume organization session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
}
|
|
|
|
return &types.AssumeOrganizationSessionPayload{
|
|
Result: types.OrganizationSessionCreated{
|
|
Session: types.NewSession(childSession),
|
|
Membership: types.NewMembership(membership),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// RevokeSession is the resolver for the revokeSession field.
|
|
func (r *mutationResolver) RevokeSession(ctx context.Context, input types.RevokeSessionInput) (*types.RevokeSessionPayload, error) {
|
|
if err := r.authorize(ctx, input.SessionID, iam.ActionSessionRevoke); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
err := r.iam.SessionService.RevokeSession(ctx, identity.ID, input.SessionID)
|
|
if err != nil {
|
|
var ErrSessionExpired *iam.ErrSessionExpired
|
|
if errors.As(err, &ErrSessionExpired) {
|
|
return &types.RevokeSessionPayload{Success: true}, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot revoke session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RevokeSessionPayload{Success: true}, nil
|
|
}
|
|
|
|
// RevokeAllSessions is the resolver for the revokeAllSessions field.
|
|
func (r *mutationResolver) RevokeAllSessions(ctx context.Context) (*types.RevokeAllSessionsPayload, error) {
|
|
if err := r.authorize(ctx, authn.SessionFromContext(ctx).ID, iam.ActionSessionRevokeAll); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
session := authn.SessionFromContext(ctx)
|
|
|
|
revokedCount, err := r.iam.SessionService.RevokeAllSessions(ctx, session.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot revoke all sessions", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RevokeAllSessionsPayload{RevokedCount: int(revokedCount)}, nil
|
|
}
|
|
|
|
// CreatePersonalAPIKey is the resolver for the createPersonalAPIKey field.
|
|
func (r *mutationResolver) CreatePersonalAPIKey(ctx context.Context, input types.CreatePersonalAPIKeyInput) (*types.CreatePersonalAPIKeyPayload, error) {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
if err := r.authorize(ctx, identity.ID, iam.ActionPersonalAPIKeyCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
userAPIKey, token, err := r.iam.AccountService.CreatePersonalAPIKey(
|
|
ctx,
|
|
identity.ID,
|
|
input.Name,
|
|
input.ExpiresAt,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot create personal api key", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreatePersonalAPIKeyPayload{
|
|
PersonalAPIKeyEdge: types.NewPersonalAPIKeyEdge(userAPIKey, coredata.PersonalAPIKeyOrderFieldCreatedAt),
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
|
|
// RevokePersonalAPIKey is the resolver for the revokePersonalAPIKey field.
|
|
func (r *mutationResolver) RevokePersonalAPIKey(ctx context.Context, input types.RevokePersonalAPIKeyInput) (*types.RevokePersonalAPIKeyPayload, error) {
|
|
if err := r.authorize(ctx, input.PersonalAPIKeyID, iam.ActionPersonalAPIKeyDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
err := r.iam.AccountService.DeletePersonalAPIKey(ctx, identity.ID, input.PersonalAPIKeyID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete personal api key", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RevokePersonalAPIKeyPayload{PersonalAPIKeyID: input.PersonalAPIKeyID}, nil
|
|
}
|
|
|
|
// CreateOrganization is the resolver for the createOrganization field.
|
|
func (r *mutationResolver) CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
// FIXME check email domain and related IDP config
|
|
// if ok := r.authorize(ctx, identity.ID, iam.ActionOrganizationCreate); !ok {
|
|
// return nil, nil
|
|
// }
|
|
|
|
var (
|
|
logoFile *iam.UploadedFile
|
|
horizontalLogoFile *iam.UploadedFile
|
|
)
|
|
|
|
if input.LogoFile != nil {
|
|
logoFile = &iam.UploadedFile{
|
|
Content: input.LogoFile.File,
|
|
Filename: input.LogoFile.Filename,
|
|
ContentType: input.LogoFile.ContentType,
|
|
Size: input.LogoFile.Size,
|
|
}
|
|
}
|
|
|
|
if input.HorizontalLogoFile != nil {
|
|
horizontalLogoFile = &iam.UploadedFile{
|
|
Content: input.HorizontalLogoFile.File,
|
|
Filename: input.HorizontalLogoFile.Filename,
|
|
ContentType: input.HorizontalLogoFile.ContentType,
|
|
Size: input.HorizontalLogoFile.Size,
|
|
}
|
|
}
|
|
organization, profile, err := r.iam.OrganizationService.CreateOrganization(
|
|
ctx,
|
|
identity.ID,
|
|
&iam.CreateOrganizationRequest{
|
|
Name: input.Name,
|
|
LogoFile: logoFile,
|
|
HorizontalLogoFile: horizontalLogoFile,
|
|
},
|
|
)
|
|
if err != nil {
|
|
if errors.Is(err, coredata.ErrResourceAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create organization", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateOrganizationPayload{
|
|
Organization: types.NewOrganization(organization),
|
|
Profile: types.NewProfile(profile),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateOrganization is the resolver for the updateOrganization field.
|
|
func (r *mutationResolver) UpdateOrganization(ctx context.Context, input types.UpdateOrganizationInput) (*types.UpdateOrganizationPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationUpdate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &iam.UpdateOrganizationRequest{
|
|
Name: input.Name,
|
|
Description: gqlutils.UnwrapOmittable(input.Description),
|
|
WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL),
|
|
Email: gqlutils.UnwrapOmittable(input.Email),
|
|
HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress),
|
|
}
|
|
|
|
if input.LogoFile != nil {
|
|
req.LogoFile = &iam.UploadedFile{
|
|
Filename: input.LogoFile.Filename,
|
|
ContentType: input.LogoFile.ContentType,
|
|
Size: input.LogoFile.Size,
|
|
Content: input.LogoFile.File,
|
|
}
|
|
}
|
|
|
|
if input.HorizontalLogoFile != nil {
|
|
req.HorizontalLogoFile = &iam.UploadedFile{
|
|
Filename: input.HorizontalLogoFile.Filename,
|
|
ContentType: input.HorizontalLogoFile.ContentType,
|
|
Size: input.HorizontalLogoFile.Size,
|
|
Content: input.HorizontalLogoFile.File,
|
|
}
|
|
}
|
|
|
|
organization, err := r.iam.OrganizationService.UpdateOrganization(
|
|
ctx,
|
|
input.OrganizationID,
|
|
req,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update organization", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.UpdateOrganizationPayload{
|
|
Organization: &types.Organization{
|
|
ID: organization.ID,
|
|
Name: organization.Name,
|
|
Description: organization.Description,
|
|
WebsiteURL: organization.WebsiteURL,
|
|
Email: organization.Email,
|
|
HeadquarterAddress: organization.HeadquarterAddress,
|
|
CreatedAt: organization.CreatedAt,
|
|
UpdatedAt: organization.UpdatedAt,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// DeleteOrganization is the resolver for the deleteOrganization field.
|
|
func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.DeleteOrganizationInput) (*types.DeleteOrganizationPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err := r.iam.OrganizationService.DeleteOrganization(ctx, input.OrganizationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete organization", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeleteOrganizationPayload{DeletedOrganizationID: input.OrganizationID}, nil
|
|
}
|
|
|
|
// DeleteOrganizationHorizontalLogo is the resolver for the deleteOrganizationHorizontalLogo field.
|
|
func (r *mutationResolver) DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error) {
|
|
panic(fmt.Errorf("not implemented: DeleteOrganizationHorizontalLogo - deleteOrganizationHorizontalLogo"))
|
|
}
|
|
|
|
// CreateUser is the resolver for the createUser field.
|
|
func (r *mutationResolver) CreateUser(ctx context.Context, input types.CreateUserInput) (*types.CreateUserPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, iam.ActionMembershipProfileCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
profile, err := r.iam.OrganizationService.CreateUser(
|
|
ctx,
|
|
&iam.CreateUserRequest{
|
|
OrganizationID: input.OrganizationID,
|
|
EmailAddress: input.EmailAddress,
|
|
Role: input.Role,
|
|
FullName: input.FullName,
|
|
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
|
|
Kind: input.Kind,
|
|
Position: input.Position,
|
|
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
|
|
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
|
|
},
|
|
)
|
|
if err != nil {
|
|
var errAlreadyExists *iam.ErrUserAlreadyExists
|
|
if errors.As(err, &errAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create user", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateUserPayload{
|
|
ProfileEdge: types.NewProfileEdge(profile, coredata.MembershipProfileOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// InviteUser is the resolver for the inviteUser field.
|
|
func (r *mutationResolver) InviteUser(ctx context.Context, input types.InviteUserInput) (*types.InviteUserPayload, error) {
|
|
if err := r.authorize(ctx, input.ProfileID, iam.ActionInvitationCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
invitation, err := r.iam.OrganizationService.InviteUser(
|
|
ctx,
|
|
&iam.CreateInvitationRequest{
|
|
OrganizationID: input.OrganizationID,
|
|
ProfileID: input.ProfileID,
|
|
},
|
|
)
|
|
if err != nil {
|
|
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
|
var errUserAlreadyExists *iam.ErrUserAlreadyExists
|
|
|
|
if errors.As(err, &errOrganizationNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
if errors.As(err, &errUserAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot invite user", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.InviteUserPayload{
|
|
InvitationEdge: types.NewInvitationEdge(invitation, coredata.InvitationOrderFieldCreatedAt),
|
|
}, nil
|
|
}
|
|
|
|
// DeactivateUser is the resolver for the deactivateUser field.
|
|
func (r *mutationResolver) DeactivateUser(ctx context.Context, input types.DeactivateUserInput) (*types.DeactivateUserPayload, error) {
|
|
if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDeactivate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err := r.iam.OrganizationService.UpdateUserState(
|
|
ctx,
|
|
input.ProfileID,
|
|
coredata.ProfileStateInactive,
|
|
)
|
|
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot deactivate profile", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeactivateUserPayload{
|
|
Success: true,
|
|
}, nil
|
|
}
|
|
|
|
// UpdateUser is the resolver for the updateUser field.
|
|
func (r *mutationResolver) UpdateUser(ctx context.Context, input types.UpdateUserInput) (*types.UpdateUserPayload, error) {
|
|
if err := r.authorize(ctx, input.ID, iam.ActionMembershipProfileUpdate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
profile, err := r.iam.OrganizationService.UpdateUser(
|
|
ctx,
|
|
&iam.UpdateUserRequest{
|
|
ID: input.ID,
|
|
FullName: input.FullName,
|
|
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
|
|
Kind: input.Kind,
|
|
Position: input.Position,
|
|
ContractStartDate: gqlutils.UnwrapOmittable(input.ContractStartDate),
|
|
ContractEndDate: gqlutils.UnwrapOmittable(input.ContractEndDate),
|
|
},
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update profile", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.UpdateUserPayload{
|
|
Profile: types.NewProfile(profile),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateMembership is the resolver for the updateMembership field.
|
|
func (r *mutationResolver) UpdateMembership(ctx context.Context, input types.UpdateMembershipInput) (*types.UpdateMembershipPayload, error) {
|
|
if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipUpdate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if input.Role == coredata.MembershipRoleOwner {
|
|
if err := r.authorize(ctx, input.MembershipID, iam.ActionMembershipRoleSetOwner); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
membership, err := r.iam.OrganizationService.UpdateMempership(ctx, input.OrganizationID, input.MembershipID, input.Role)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update membership", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.UpdateMembershipPayload{
|
|
Membership: types.NewMembership(membership),
|
|
}, nil
|
|
}
|
|
|
|
// RemoveUser is the resolver for the removeUser field.
|
|
func (r *mutationResolver) RemoveUser(ctx context.Context, input types.RemoveUserInput) (*types.RemoveUserPayload, error) {
|
|
if err := r.authorize(ctx, input.ProfileID, iam.ActionMembershipProfileDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err := r.iam.OrganizationService.RemoveUser(ctx, input.OrganizationID, input.ProfileID)
|
|
if err != nil {
|
|
var errManagedBySCIM *iam.ErrUserManagedBySCIM
|
|
var errLastActiveOwner *iam.ErrLastActiveOwner
|
|
|
|
if errors.As(err, &errManagedBySCIM) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
if errors.As(err, &errLastActiveOwner) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot remove user from organization", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RemoveUserPayload{DeletedProfileID: input.ProfileID}, nil
|
|
}
|
|
|
|
// CreateSAMLConfiguration is the resolver for the createSAMLConfiguration field.
|
|
func (r *mutationResolver) CreateSAMLConfiguration(ctx context.Context, input types.CreateSAMLConfigurationInput) (*types.CreateSAMLConfigurationPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &iam.CreateSAMLConfigurationRequest{
|
|
EmailDomain: input.EmailDomain,
|
|
IdPEntityID: input.IdpEntityID,
|
|
IdPSsoURL: input.IdpSsoURL,
|
|
IdPCertificate: input.IdpCertificate,
|
|
AutoSignupEnabled: input.AutoSignupEnabled,
|
|
}
|
|
|
|
if input.AttributeMappings != nil {
|
|
req.AttributeEmail = input.AttributeMappings.Email
|
|
req.AttributeFirstname = input.AttributeMappings.FirstName
|
|
req.AttributeLastname = input.AttributeMappings.LastName
|
|
req.AttributeRole = input.AttributeMappings.Role
|
|
}
|
|
|
|
samlConfiguration, err := r.iam.OrganizationService.CreateSAMLConfiguration(
|
|
ctx,
|
|
input.OrganizationID,
|
|
req,
|
|
)
|
|
|
|
if err != nil {
|
|
var errSAMLConfigurationEmailDomainAlreadyExists *iam.ErrSAMLConfigurationEmailDomainAlreadyExists
|
|
if errors.As(err, &errSAMLConfigurationEmailDomainAlreadyExists) {
|
|
return nil, gqlutils.Conflict(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot create saml configuration", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.CreateSAMLConfigurationPayload{
|
|
SamlConfigurationEdge: types.NewSAMLConfigurationEdge(
|
|
samlConfiguration,
|
|
coredata.SAMLConfigurationOrderFieldCreatedAt,
|
|
),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateSAMLConfiguration is the resolver for the updateSAMLConfiguration field.
|
|
func (r *mutationResolver) UpdateSAMLConfiguration(ctx context.Context, input types.UpdateSAMLConfigurationInput) (*types.UpdateSAMLConfigurationPayload, error) {
|
|
if err := r.authorize(ctx, input.SamlConfigurationID, iam.ActionSAMLConfigurationUpdate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &iam.UpdateSAMLConfigurationRequest{
|
|
IdPEntityID: input.IdpEntityID,
|
|
IdPSsoURL: input.IdpSsoURL,
|
|
IdPCertificate: input.IdpCertificate,
|
|
AutoSignupEnabled: input.AutoSignupEnabled,
|
|
EnforcementPolicy: &input.EnforcementPolicy,
|
|
}
|
|
|
|
if input.AttributeMappings != nil {
|
|
req.AttributeEmail = input.AttributeMappings.Email
|
|
req.AttributeFirstname = input.AttributeMappings.FirstName
|
|
req.AttributeLastname = input.AttributeMappings.LastName
|
|
req.AttributeRole = input.AttributeMappings.Role
|
|
}
|
|
|
|
samlConfiguration, err := r.iam.OrganizationService.UpdateSAMLConfiguration(
|
|
ctx,
|
|
input.OrganizationID,
|
|
input.SamlConfigurationID,
|
|
req,
|
|
)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update saml configuration", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.UpdateSAMLConfigurationPayload{
|
|
SamlConfiguration: types.NewSAMLConfiguration(samlConfiguration),
|
|
}, nil
|
|
}
|
|
|
|
// DeleteSAMLConfiguration is the resolver for the deleteSAMLConfiguration field.
|
|
func (r *mutationResolver) DeleteSAMLConfiguration(ctx context.Context, input types.DeleteSAMLConfigurationInput) (*types.DeleteSAMLConfigurationPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSAMLConfigurationDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err := r.iam.OrganizationService.DeleteSAMLConfiguration(ctx, input.OrganizationID, input.SamlConfigurationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete saml configuration", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeleteSAMLConfigurationPayload{DeletedSamlConfigurationID: input.SamlConfigurationID}, nil
|
|
}
|
|
|
|
// CreateSCIMConfiguration is the resolver for the createSCIMConfiguration field.
|
|
func (r *mutationResolver) CreateSCIMConfiguration(ctx context.Context, input types.CreateSCIMConfigurationInput) (*types.CreateSCIMConfigurationPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationCreate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config, token, err := r.iam.OrganizationService.CreateSCIMConfiguration(ctx, input.OrganizationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot create scim configuration", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
var bridge *types.SCIMBridge
|
|
|
|
if input.ConnectorID != nil {
|
|
scimBridge, err := r.iam.OrganizationService.CreateSCIMBridge(ctx, input.OrganizationID, config.ID, *input.ConnectorID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot create scim bridge", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
bridge = types.NewSCIMBridge(scimBridge)
|
|
}
|
|
|
|
payload := &types.CreateSCIMConfigurationPayload{
|
|
ScimConfiguration: types.NewSCIMConfiguration(config),
|
|
ScimBridge: bridge,
|
|
Token: token,
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
// DeleteSCIMConfiguration is the resolver for the deleteSCIMConfiguration field.
|
|
func (r *mutationResolver) DeleteSCIMConfiguration(ctx context.Context, input types.DeleteSCIMConfigurationInput) (*types.DeleteSCIMConfigurationPayload, error) {
|
|
if err := r.authorize(ctx, input.OrganizationID, iam.ActionSCIMConfigurationDelete); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err := r.iam.OrganizationService.DeleteSCIMConfiguration(ctx, input.OrganizationID, input.ScimConfigurationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot delete scim configuration", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.DeleteSCIMConfigurationPayload{DeletedScimConfigurationID: input.ScimConfigurationID}, nil
|
|
}
|
|
|
|
// RegenerateSCIMToken is the resolver for the regenerateSCIMToken field.
|
|
func (r *mutationResolver) RegenerateSCIMToken(ctx context.Context, input types.RegenerateSCIMTokenInput) (*types.RegenerateSCIMTokenPayload, error) {
|
|
if err := r.authorize(ctx, input.ScimConfigurationID, iam.ActionSCIMConfigurationUpdate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config, token, err := r.iam.OrganizationService.RegenerateSCIMToken(ctx, input.OrganizationID, input.ScimConfigurationID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot regenerate scim token", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.RegenerateSCIMTokenPayload{
|
|
ScimConfiguration: types.NewSCIMConfiguration(config),
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
|
|
// UpdateSCIMBridge is the resolver for the updateSCIMBridge field.
|
|
func (r *mutationResolver) UpdateSCIMBridge(ctx context.Context, input types.UpdateSCIMBridgeInput) (*types.UpdateSCIMBridgePayload, error) {
|
|
if err := r.authorize(ctx, input.ScimBridgeID, iam.ActionSCIMBridgeUpdate); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bridge, err := r.iam.OrganizationService.UpdateSCIMBridge(ctx, input.OrganizationID, input.ScimBridgeID, input.ExcludedUserNames)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot update scim bridge excluded user names", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &types.UpdateSCIMBridgePayload{
|
|
ScimBridge: types.NewSCIMBridge(bridge),
|
|
}, nil
|
|
}
|
|
|
|
// LogoURL is the resolver for the logoUrl field.
|
|
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
presignedURL, err := r.iam.OrganizationService.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot generate logo URL", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return presignedURL, nil
|
|
}
|
|
|
|
// HorizontalLogoURL is the resolver for the horizontalLogoUrl field.
|
|
func (r *organizationResolver) HorizontalLogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionOrganizationGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
presignedURL, err := r.iam.OrganizationService.GenerateHorizontalLogoURL(ctx, obj.ID, 1*time.Hour)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot generate horizontal logo URL", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return presignedURL, nil
|
|
}
|
|
|
|
// Profiles is the resolver for the profiles field.
|
|
func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filter := coredata.NewMembershipProfileFilter(nil).WithMembership()
|
|
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.ProfileConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
Filters: filter,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
|
|
Field: coredata.MembershipProfileOrderFieldFullName,
|
|
Direction: page.OrderDirectionAsc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.MembershipProfileOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.OrganizationService.ListProfiles(ctx, obj.ID, cursor, filter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list profiles", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewProfileConnection(page, r, obj.ID, filter), nil
|
|
}
|
|
|
|
// SamlConfigurations is the resolver for the samlConfigurations field.
|
|
func (r *organizationResolver) SamlConfigurations(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SAMLConfigurationConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionSAMLConfigurationList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gqlutils.OnlyTotalCountSelected(ctx) {
|
|
return &types.SAMLConfigurationConnection{
|
|
Resolver: r,
|
|
ParentID: obj.ID,
|
|
}, nil
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.SAMLConfigurationOrderField]{
|
|
Field: coredata.SAMLConfigurationOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.OrganizationService.ListSAMLConfigurations(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list saml configurations", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewSAMLConfigurationConnection(page, r, obj.ID), nil
|
|
}
|
|
|
|
// ScimConfiguration is the resolver for the scimConfiguration field.
|
|
func (r *organizationResolver) ScimConfiguration(ctx context.Context, obj *types.Organization) (*types.SCIMConfiguration, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config, err := r.iam.OrganizationService.GetSCIMConfiguration(ctx, obj.ID)
|
|
if err != nil {
|
|
var notFound *iam.ErrNoSCIMConfigurationFound
|
|
if errors.As(err, ¬Found) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get scim configuration", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewSCIMConfiguration(config), nil
|
|
}
|
|
|
|
// ScimBridgeTypes is the resolver for the scimBridgeTypes field.
|
|
func (r *organizationResolver) ScimBridgeTypes(ctx context.Context, obj *types.Organization) ([]*types.SCIMBridgeTypeInfo, error) {
|
|
return []*types.SCIMBridgeTypeInfo{
|
|
{
|
|
Type: coredata.SCIMBridgeTypeGoogleWorkspace,
|
|
Oauth2Scopes: googleworkspace.OAuth2Scopes,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// AuditLogEntries is the resolver for the auditLogEntries field.
|
|
func (r *organizationResolver) AuditLogEntries(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditLogEntryOrderBy, filter *types.AuditLogEntryFilter) (*types.AuditLogEntryConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionAuditLogEntryList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.AuditLogEntryOrderField]{
|
|
Field: coredata.AuditLogEntryOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy = page.OrderBy[coredata.AuditLogEntryOrderField]{
|
|
Field: orderBy.Field,
|
|
Direction: orderBy.Direction,
|
|
}
|
|
}
|
|
|
|
c := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
coredataFilter := coredata.NewAuditLogEntryFilter()
|
|
if filter != nil {
|
|
if filter.Action != nil {
|
|
coredataFilter.WithAction(*filter.Action)
|
|
}
|
|
if filter.ActorID != nil {
|
|
coredataFilter.WithActorID(*filter.ActorID)
|
|
}
|
|
if filter.ResourceType != nil {
|
|
coredataFilter.WithResourceType(*filter.ResourceType)
|
|
}
|
|
if filter.ResourceID != nil {
|
|
coredataFilter.WithResourceID(*filter.ResourceID)
|
|
}
|
|
}
|
|
|
|
p, err := r.iam.OrganizationService.ListAuditLogEntries(ctx, obj.ID, c, coredataFilter)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list audit log entries", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewAuditLogEntryConnection(p, r, obj.ID, coredataFilter), nil
|
|
}
|
|
|
|
// Viewer is the resolver for the viewer field.
|
|
func (r *organizationResolver) Viewer(ctx context.Context, obj *types.Organization) (*types.Profile, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
profile, err := r.iam.OrganizationService.GetProfileForIdentityAndOrganization(ctx, identity.ID, obj.ID)
|
|
if err != nil {
|
|
var errNotFound *iam.ErrProfileNotFound
|
|
if errors.As(err, &errNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get profile", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewProfile(profile), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// Token is the resolver for the token field.
|
|
func (r *personalAPIKeyResolver) Token(ctx context.Context, obj *types.PersonalAPIKey) (*string, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionPersonalAPIKeyGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
token, err := r.iam.AccountService.RevealPersonalAPIKeyToken(ctx, identity.ID, obj.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot reveal personal api key token", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &token, nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *personalAPIKeyResolver) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *identityResolver:
|
|
if err := r.authorize(ctx, obj.ParentID, iam.ActionPersonalAPIKeyList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
count, err := r.iam.AccountService.CountPersonalAPIKeys(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count personal api keys", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// Identity is the resolver for the identity field.
|
|
func (r *profileResolver) Identity(ctx context.Context, obj *types.Profile) (*types.Identity, error) {
|
|
if err := r.authorize(
|
|
ctx,
|
|
obj.ID,
|
|
iam.ActionMembershipProfileGet,
|
|
authz.WithSkipAssumptionCheck(),
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
|
|
if err != nil {
|
|
var errNotFound *iam.ErrIdentityNotFound
|
|
if errors.As(err, &errNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get identity", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewIdentity(identity), nil
|
|
}
|
|
|
|
// Organization is the resolver for the organization field.
|
|
func (r *profileResolver) Organization(ctx context.Context, obj *types.Profile) (*types.Organization, error) {
|
|
if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet, authz.WithSkipAssumptionCheck()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID)
|
|
if err != nil {
|
|
var errNotFound *iam.ErrOrganizationNotFound
|
|
if errors.As(err, &errNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get organization", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewOrganization(organization), nil
|
|
}
|
|
|
|
// Membership is the resolver for the membership field.
|
|
func (r *profileResolver) Membership(ctx context.Context, obj *types.Profile) (*types.Membership, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionMembershipGet, authz.WithSkipAssumptionCheck()); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
membership, err := r.iam.AccountService.GetMembershipForOrganization(ctx, obj.Identity.ID, obj.Organization.ID)
|
|
if err != nil {
|
|
var errNotFound *iam.ErrMembershipNotFound
|
|
if errors.As(err, &errNotFound) {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get membership", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewMembership(membership), nil
|
|
}
|
|
|
|
// PendingInvitations is the resolver for the pendingInvitations field.
|
|
func (r *profileResolver) PendingInvitations(ctx context.Context, obj *types.Profile, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.InvitationOrderBy) (*types.InvitationConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionInvitationList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.InvitationOrderField]{
|
|
Field: coredata.InvitationOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
page, err := r.iam.AccountService.ListPendingInvitations(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list pending invitations", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewInvitationConnection(page, r, obj.ID, nil), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *profileResolver) Permission(ctx context.Context, obj *types.Profile, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.ProfileConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *identityResolver:
|
|
count, err := r.iam.AccountService.CountProfiles(ctx, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
return &count, nil
|
|
case *organizationResolver:
|
|
count, err := r.iam.OrganizationService.CountProfiles(ctx, obj.ParentID, obj.Filters)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count profiles", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// Node is the resolver for the node field.
|
|
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
var (
|
|
loadNode func(ctx context.Context, id gid.GID) (types.Node, error)
|
|
action string
|
|
)
|
|
|
|
switch id.EntityType() {
|
|
case coredata.OrganizationEntityType:
|
|
action = iam.ActionOrganizationGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
organization, err := r.iam.OrganizationService.GetOrganization(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return types.NewOrganization(organization), nil
|
|
}
|
|
case coredata.IdentityEntityType:
|
|
action = iam.ActionIdentityGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
identity, err := r.iam.AccountService.GetIdentity(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewIdentity(identity), nil
|
|
}
|
|
case coredata.SessionEntityType:
|
|
action = iam.ActionSessionGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
session, err := r.iam.GetSession(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewSession(session), nil
|
|
}
|
|
case coredata.MembershipProfileEntityType:
|
|
action = iam.ActionMembershipGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
profile, err := r.iam.OrganizationService.GetProfile(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewProfile(profile), nil
|
|
}
|
|
case coredata.MembershipEntityType:
|
|
action = iam.ActionMembershipGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
membership, err := r.iam.GetMembership(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewMembership(membership), nil
|
|
}
|
|
case coredata.InvitationEntityType:
|
|
action = iam.ActionInvitationGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
invitation, err := r.iam.GetInvitation(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewInvitation(invitation), nil
|
|
}
|
|
case coredata.SAMLConfigurationEntityType:
|
|
action = iam.ActionSAMLConfigurationGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
samlConfiguration, err := r.iam.GetSAMLconfiguration(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewSAMLConfiguration(samlConfiguration), nil
|
|
}
|
|
case coredata.PersonalAPIKeyEntityType:
|
|
action = iam.ActionPersonalAPIKeyGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
personalAPIKey, err := r.iam.GetPersonalAPIKey(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return types.NewPersonalAPIKey(personalAPIKey), nil
|
|
}
|
|
case coredata.SCIMConfigurationEntityType:
|
|
action = iam.ActionSCIMConfigurationGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return types.NewSCIMConfiguration(scimConfiguration), nil
|
|
}
|
|
case coredata.SCIMEventEntityType:
|
|
action = iam.ActionSCIMEventGet
|
|
loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) {
|
|
scimEvent, err := r.iam.GetSCIMEvent(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return types.NewSCIMEvent(scimEvent), nil
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported entity type: %d", id.EntityType())
|
|
}
|
|
|
|
if err := r.authorize(ctx, id, action); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
node, err := loadNode(ctx, id)
|
|
if err != nil {
|
|
var (
|
|
errOrganizationNotFound *iam.ErrOrganizationNotFound
|
|
errIdentityNotFound *iam.ErrIdentityNotFound
|
|
errSessionNotFound *iam.ErrSessionNotFound
|
|
errProfileNotFound *iam.ErrProfileNotFound
|
|
errMembershipNotFound *iam.ErrMembershipNotFound
|
|
errInvitationNotFound *iam.ErrInvitationNotFound
|
|
|
|
isNotFoundErr = errors.As(err, &errOrganizationNotFound) ||
|
|
errors.As(err, &errIdentityNotFound) ||
|
|
errors.As(err, &errSessionNotFound) ||
|
|
errors.As(err, &errProfileNotFound) ||
|
|
errors.As(err, &errMembershipNotFound) ||
|
|
errors.As(err, &errInvitationNotFound)
|
|
)
|
|
|
|
if isNotFoundErr {
|
|
return nil, gqlutils.NotFound(ctx, err)
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot load node", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
// Viewer is the resolver for the viewer field.
|
|
func (r *queryResolver) Viewer(ctx context.Context) (*types.Identity, error) {
|
|
identity := authn.IdentityFromContext(ctx)
|
|
|
|
return &types.Identity{
|
|
ID: identity.ID,
|
|
Email: identity.EmailAddress,
|
|
EmailVerified: identity.EmailAddressVerified,
|
|
FullName: identity.FullName,
|
|
CreatedAt: identity.CreatedAt,
|
|
UpdatedAt: identity.UpdatedAt,
|
|
}, nil
|
|
}
|
|
|
|
// SsoLoginURL is the resolver for the ssoLoginURL field.
|
|
func (r *queryResolver) SsoLoginURL(ctx context.Context, email mail.Addr) (*string, error) {
|
|
count, err := r.iam.AccountService.CountSAMLConfigurationsForEmail(ctx, email)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count SAML configurations for email", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
if count != 1 {
|
|
if count == 0 {
|
|
return nil, graphql.ErrorOnPath(
|
|
ctx,
|
|
fmt.Errorf("no SAML configuration for email"),
|
|
)
|
|
}
|
|
|
|
return nil, graphql.ErrorOnPath(
|
|
ctx,
|
|
fmt.Errorf("multiple SSO configurations found for this domain. Please use your organization-specific SSO login URL"),
|
|
)
|
|
}
|
|
|
|
samlConfigs, err := r.iam.AccountService.ListSAMLConfigurationsForEmail(ctx, email)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list SAML configurations for email", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
samlConfig := samlConfigs[0]
|
|
loginURL := r.SSOLoginURL(samlConfig.ID)
|
|
|
|
return &loginURL, nil
|
|
}
|
|
|
|
// OidcProviders is the resolver for the oidcProviders field.
|
|
func (r *queryResolver) OidcProviders(ctx context.Context) ([]*types.OIDCProviderInfo, error) {
|
|
providers := r.iam.OIDCService.EnabledProviders()
|
|
result := make([]*types.OIDCProviderInfo, 0, len(providers))
|
|
|
|
for _, p := range providers {
|
|
result = append(result, &types.OIDCProviderInfo{
|
|
Name: strings.ToLower(p.String()),
|
|
LoginURL: r.baseURL.WithPath("/api/connect/v1/oidc/" + strings.ToLower(p.String()) + "/login").MustString(),
|
|
})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// SignUpEnabled is the resolver for the signUpEnabled field.
|
|
func (r *queryResolver) SignUpEnabled(ctx context.Context) (bool, error) {
|
|
return r.iam.IsSignUpEnabled(), nil
|
|
}
|
|
|
|
// TestLoginURL is the resolver for the testLoginUrl field.
|
|
func (r *sAMLConfigurationResolver) TestLoginURL(ctx context.Context, obj *types.SAMLConfiguration) (string, error) {
|
|
return r.baseURL.WithPath("/api/connect/v1/saml/2.0/" + obj.ID.String()).MustString(), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *sAMLConfigurationResolver) Permission(ctx context.Context, obj *types.SAMLConfiguration, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, obj *types.SAMLConfigurationConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *organizationResolver:
|
|
count, err := r.iam.OrganizationService.CountSAMLConfigurations(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count saml configurations", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// ScimConfiguration is the resolver for the scimConfiguration field.
|
|
func (r *sCIMBridgeResolver) ScimConfiguration(ctx context.Context, obj *types.SCIMBridge) (*types.SCIMConfiguration, error) {
|
|
if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.SCIMConfiguration{
|
|
ID: obj.ScimConfiguration.ID,
|
|
}, nil
|
|
}
|
|
|
|
scimConfiguration, err := r.iam.GetSCIMConfiguration(ctx, obj.ScimConfiguration.ID)
|
|
if err != nil {
|
|
var errNoSCIMConfigurationFound *iam.ErrNoSCIMConfigurationFound
|
|
if errors.As(err, &errNoSCIMConfigurationFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return types.NewSCIMConfiguration(scimConfiguration), nil
|
|
}
|
|
|
|
// Connector is the resolver for the connector field.
|
|
func (r *sCIMBridgeResolver) Connector(ctx context.Context, obj *types.SCIMBridge) (*types.Connector, error) {
|
|
if obj.Connector == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Authorize based on the SCIM configuration (connector accessed via bridge is a sub-resource)
|
|
if err := r.authorize(ctx, obj.ScimConfiguration.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.Connector{
|
|
ID: obj.Connector.ID,
|
|
}, nil
|
|
}
|
|
|
|
// Use metadata-only loading since we don't need the encrypted connection data
|
|
connector, err := r.iam.OrganizationService.GetConnectorMetadataByID(ctx, obj.Connector.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get connector", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewConnector(connector), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *sCIMBridgeResolver) Permission(ctx context.Context, obj *types.SCIMBridge, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// EndpointURL is the resolver for the endpointUrl field.
|
|
func (r *sCIMConfigurationResolver) EndpointURL(ctx context.Context, obj *types.SCIMConfiguration) (string, error) {
|
|
return r.baseURL.WithPath("/api/connect/v1/scim/2.0").MustString(), nil
|
|
}
|
|
|
|
// Organization is the resolver for the organization field.
|
|
func (r *sCIMConfigurationResolver) Organization(ctx context.Context, obj *types.SCIMConfiguration) (*types.Organization, error) {
|
|
if err := r.authorize(ctx, obj.Organization.ID, iam.ActionOrganizationGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.Organization{
|
|
ID: obj.Organization.ID,
|
|
}, nil
|
|
}
|
|
|
|
organization, err := r.iam.OrganizationService.GetOrganization(ctx, obj.Organization.ID)
|
|
if err != nil {
|
|
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
|
if errors.As(err, &errOrganizationNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get organization for scim configuration", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewOrganization(organization), nil
|
|
}
|
|
|
|
// Bridge is the resolver for the bridge field.
|
|
func (r *sCIMConfigurationResolver) Bridge(ctx context.Context, obj *types.SCIMConfiguration) (*types.SCIMBridge, error) {
|
|
if obj.Bridge == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionSCIMConfigurationGet); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bridge, err := r.iam.OrganizationService.GetSCIMBridgeByID(ctx, obj.Bridge.ID)
|
|
if err != nil {
|
|
var errSCIMBridgeNotFound *iam.ErrSCIMBridgeNotFound
|
|
if errors.As(err, &errSCIMBridgeNotFound) {
|
|
return nil, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "cannot get scim bridge", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewSCIMBridge(bridge), nil
|
|
}
|
|
|
|
// Events is the resolver for the events field.
|
|
func (r *sCIMConfigurationResolver) Events(ctx context.Context, obj *types.SCIMConfiguration, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SCIMEventOrderBy) (*types.SCIMEventConnection, error) {
|
|
if err := r.authorize(ctx, obj.ID, iam.ActionSCIMEventList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pageOrderBy := page.OrderBy[coredata.SCIMEventOrderField]{
|
|
Field: coredata.SCIMEventOrderFieldCreatedAt,
|
|
Direction: page.OrderDirectionDesc,
|
|
}
|
|
if orderBy != nil {
|
|
pageOrderBy.Field = coredata.SCIMEventOrderField(orderBy.Field)
|
|
pageOrderBy.Direction = page.OrderDirection(orderBy.Direction)
|
|
}
|
|
|
|
cursor := cursor.NewCursor(first, after, last, before, pageOrderBy)
|
|
|
|
events, err := r.iam.OrganizationService.ListSCIMEventsByConfigID(ctx, obj.ID, cursor)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot list scim events", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewSCIMEventConnection(events, r, obj.ID), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *sCIMConfigurationResolver) Permission(ctx context.Context, obj *types.SCIMConfiguration, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *sCIMEventResolver) Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *sCIMEventConnectionResolver) TotalCount(ctx context.Context, obj *types.SCIMEventConnection) (*int, error) {
|
|
if err := r.authorize(ctx, obj.ParentID, iam.ActionSCIMEventList); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch obj.Resolver.(type) {
|
|
case *sCIMConfigurationResolver:
|
|
count, err := r.iam.OrganizationService.CountSCIMEvents(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count scim events", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// Identity is the resolver for the identity field.
|
|
func (r *sessionResolver) Identity(ctx context.Context, obj *types.Session) (*types.Identity, error) {
|
|
if gqlutils.OnlyIDSelected(ctx) {
|
|
return &types.Identity{
|
|
ID: obj.Identity.ID,
|
|
}, nil
|
|
}
|
|
|
|
identity, err := r.iam.AccountService.GetIdentity(ctx, obj.Identity.ID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot get identity for session", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return types.NewIdentity(identity), nil
|
|
}
|
|
|
|
// Permission is the resolver for the permission field.
|
|
func (r *sessionResolver) Permission(ctx context.Context, obj *types.Session, action string) (bool, error) {
|
|
return r.Resolver.Permission(ctx, obj, action)
|
|
}
|
|
|
|
// TotalCount is the resolver for the totalCount field.
|
|
func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (*int, error) {
|
|
switch obj.Resolver.(type) {
|
|
case *identityResolver:
|
|
count, err := r.iam.AccountService.CountSessions(ctx, obj.ParentID)
|
|
if err != nil {
|
|
r.logger.ErrorCtx(ctx, "cannot count sessions", log.Error(err))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
return &count, nil
|
|
}
|
|
|
|
r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver))
|
|
return nil, gqlutils.Internal(ctx)
|
|
}
|
|
|
|
// AuditLogEntry returns schema.AuditLogEntryResolver implementation.
|
|
func (r *Resolver) AuditLogEntry() schema.AuditLogEntryResolver { return &auditLogEntryResolver{r} }
|
|
|
|
// AuditLogEntryConnection returns schema.AuditLogEntryConnectionResolver implementation.
|
|
func (r *Resolver) AuditLogEntryConnection() schema.AuditLogEntryConnectionResolver {
|
|
return &auditLogEntryConnectionResolver{r}
|
|
}
|
|
|
|
// Connector returns schema.ConnectorResolver implementation.
|
|
func (r *Resolver) Connector() schema.ConnectorResolver { return &connectorResolver{r} }
|
|
|
|
// Identity returns schema.IdentityResolver implementation.
|
|
func (r *Resolver) Identity() schema.IdentityResolver { return &identityResolver{r} }
|
|
|
|
// Invitation returns schema.InvitationResolver implementation.
|
|
func (r *Resolver) Invitation() schema.InvitationResolver { return &invitationResolver{r} }
|
|
|
|
// Membership returns schema.MembershipResolver implementation.
|
|
func (r *Resolver) Membership() schema.MembershipResolver { return &membershipResolver{r} }
|
|
|
|
// Mutation returns schema.MutationResolver implementation.
|
|
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
|
|
|
|
// Organization returns schema.OrganizationResolver implementation.
|
|
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
|
|
|
|
// PersonalAPIKey returns schema.PersonalAPIKeyResolver implementation.
|
|
func (r *Resolver) PersonalAPIKey() schema.PersonalAPIKeyResolver { return &personalAPIKeyResolver{r} }
|
|
|
|
// PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation.
|
|
func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver {
|
|
return &personalAPIKeyConnectionResolver{r}
|
|
}
|
|
|
|
// Profile returns schema.ProfileResolver implementation.
|
|
func (r *Resolver) Profile() schema.ProfileResolver { return &profileResolver{r} }
|
|
|
|
// ProfileConnection returns schema.ProfileConnectionResolver implementation.
|
|
func (r *Resolver) ProfileConnection() schema.ProfileConnectionResolver {
|
|
return &profileConnectionResolver{r}
|
|
}
|
|
|
|
// Query returns schema.QueryResolver implementation.
|
|
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
|
|
|
|
// SAMLConfiguration returns schema.SAMLConfigurationResolver implementation.
|
|
func (r *Resolver) SAMLConfiguration() schema.SAMLConfigurationResolver {
|
|
return &sAMLConfigurationResolver{r}
|
|
}
|
|
|
|
// SAMLConfigurationConnection returns schema.SAMLConfigurationConnectionResolver implementation.
|
|
func (r *Resolver) SAMLConfigurationConnection() schema.SAMLConfigurationConnectionResolver {
|
|
return &sAMLConfigurationConnectionResolver{r}
|
|
}
|
|
|
|
// SCIMBridge returns schema.SCIMBridgeResolver implementation.
|
|
func (r *Resolver) SCIMBridge() schema.SCIMBridgeResolver { return &sCIMBridgeResolver{r} }
|
|
|
|
// SCIMConfiguration returns schema.SCIMConfigurationResolver implementation.
|
|
func (r *Resolver) SCIMConfiguration() schema.SCIMConfigurationResolver {
|
|
return &sCIMConfigurationResolver{r}
|
|
}
|
|
|
|
// SCIMEvent returns schema.SCIMEventResolver implementation.
|
|
func (r *Resolver) SCIMEvent() schema.SCIMEventResolver { return &sCIMEventResolver{r} }
|
|
|
|
// SCIMEventConnection returns schema.SCIMEventConnectionResolver implementation.
|
|
func (r *Resolver) SCIMEventConnection() schema.SCIMEventConnectionResolver {
|
|
return &sCIMEventConnectionResolver{r}
|
|
}
|
|
|
|
// Session returns schema.SessionResolver implementation.
|
|
func (r *Resolver) Session() schema.SessionResolver { return &sessionResolver{r} }
|
|
|
|
// SessionConnection returns schema.SessionConnectionResolver implementation.
|
|
func (r *Resolver) SessionConnection() schema.SessionConnectionResolver {
|
|
return &sessionConnectionResolver{r}
|
|
}
|
|
|
|
type auditLogEntryResolver struct{ *Resolver }
|
|
type auditLogEntryConnectionResolver struct{ *Resolver }
|
|
type connectorResolver struct{ *Resolver }
|
|
type identityResolver struct{ *Resolver }
|
|
type invitationResolver struct{ *Resolver }
|
|
type membershipResolver struct{ *Resolver }
|
|
type mutationResolver struct{ *Resolver }
|
|
type organizationResolver struct{ *Resolver }
|
|
type personalAPIKeyResolver struct{ *Resolver }
|
|
type personalAPIKeyConnectionResolver struct{ *Resolver }
|
|
type profileResolver struct{ *Resolver }
|
|
type profileConnectionResolver struct{ *Resolver }
|
|
type queryResolver struct{ *Resolver }
|
|
type sAMLConfigurationResolver struct{ *Resolver }
|
|
type sAMLConfigurationConnectionResolver struct{ *Resolver }
|
|
type sCIMBridgeResolver struct{ *Resolver }
|
|
type sCIMConfigurationResolver struct{ *Resolver }
|
|
type sCIMEventResolver struct{ *Resolver }
|
|
type sCIMEventConnectionResolver struct{ *Resolver }
|
|
type sessionResolver struct{ *Resolver }
|
|
type sessionConnectionResolver struct{ *Resolver }
|