From eea19f27a27a95c4df96a5591529a06c39c9e6e8 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Sun, 2 Aug 2026 10:31:29 +0530 Subject: [PATCH] feat: rest + grpc for orgs, SSO, SCIM, org domains; full protocol coverage 26 admin methods were gqlOnly because the server had no RPC for them. Server 2.4.0 (authorizer #739) added the proto RPCs and REST bindings for organizations, org members, org domains, org OIDC/SAML connections and SCIM endpoints, so they now span all three protocols. A further 5 -- AdminLogout, AdminSession, AdminMeta, FgaGetModel and FgaReset -- were rest+grpc-only despite each having a GraphQL op on the server; the SDK simply carried no query for them. That leaves AdminSignup, UpdateEnv and GenerateJWTKeys graphql-only, the only admin operations with no proto RPC. Not a breaking change: the 26 keep their hand-written request/response types. Those return the bare domain object while the proto response wraps it, so adminMethodSpec gains responseUnwrap -- the dual of the existing graphqlWrap. AdminMeta and FgaGetModel need the opposite, since their proto responses nest while the GraphQL ops return the object directly. REST for these decodes into the proto message first, via the new restResponse. grpc-gateway emits int64 as a JSON string and doREST only applies protojson to proto.Message targets, so decoding straight into a domain type failed on every timestamp. A wrong unwrap yields a zero-valued struct rather than an error, so the new cross-protocol tests assert real field values. Requires authorizer-proto-go v0.2.0-rc.0. Integration tests now run against the 2.4.0-rc.13 image. --- .github/workflows/test.yml | 2 +- Makefile | 2 +- admin_accessors.go | 409 +++++++++++++++++++++++++ admin_client.go | 50 ++- admin_methods.go | 603 ++++++++++++++++++++++++++++++------- go.mod | 2 +- go.sum | 4 +- test/admin_test.go | 61 ++-- 8 files changed, 979 insertions(+), 154 deletions(-) create mode 100644 admin_accessors.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6de8025..98ea94e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: echo "These files need gofmt:"; echo "$unformatted"; exit 1 fi - # make test starts the quay.io/authorizer/authorizer:2.4.0-rc.9 container + # make test starts the quay.io/authorizer/authorizer:2.4.0-rc.13 container # (docker is preinstalled on ubuntu-latest), runs the integration suite # across graphql/rest/grpc, then tears the container down. - name: Integration tests diff --git a/Makefile b/Makefile index 7db7c3d..b4e4c9e 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # 2. Run tests: make test # Docker image for authorizer server -AUTHORIZER_IMAGE := quay.io/authorizer/authorizer:2.4.0-rc.9 +AUTHORIZER_IMAGE := quay.io/authorizer/authorizer:2.4.0-rc.13 AUTHORIZER_CONTAINER := authorizer-test .PHONY: docker-up docker-down test diff --git a/admin_accessors.go b/admin_accessors.go new file mode 100644 index 0000000..ccb4fa3 --- /dev/null +++ b/admin_accessors.go @@ -0,0 +1,409 @@ +package authorizer + +import ( + authorizerv1 "github.com/authorizerdev/authorizer-proto-go/authorizer/v1" +) + +// Nil-safe accessors for the organization / org SSO / SCIM / org domain request +// types. The gRPC path builds a proto message field by field, and these keep +// that construction safe when the caller passes a nil request (valid for the +// list operations, whose GraphQL params are optional). +func (r *CreateOrganizationRequest) GetName() string { + if r == nil { + return "" + } + return r.Name +} +func (r *CreateOrganizationRequest) GetDisplayName() *string { + if r == nil { + return nil + } + return r.DisplayName +} +func (r *UpdateOrganizationRequest) GetID() string { + if r == nil { + return "" + } + return r.ID +} +func (r *UpdateOrganizationRequest) GetName() *string { + if r == nil { + return nil + } + return r.Name +} +func (r *UpdateOrganizationRequest) GetDisplayName() *string { + if r == nil { + return nil + } + return r.DisplayName +} +func (r *UpdateOrganizationRequest) GetEnabled() *bool { + if r == nil { + return nil + } + return r.Enabled +} +func (r *OrganizationRequest) GetID() string { + if r == nil { + return "" + } + return r.ID +} +func (r *AddOrgMemberRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *AddOrgMemberRequest) GetUserID() string { + if r == nil { + return "" + } + return r.UserID +} +func (r *AddOrgMemberRequest) GetRoles() []string { + if r == nil { + return nil + } + return r.Roles +} +func (r *RemoveOrgMemberRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *RemoveOrgMemberRequest) GetUserID() string { + if r == nil { + return "" + } + return r.UserID +} +func (r *ListOrgMembersRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *CreateOrgOIDCConnectionRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *CreateOrgOIDCConnectionRequest) GetName() string { + if r == nil { + return "" + } + return r.Name +} +func (r *CreateOrgOIDCConnectionRequest) GetIssuerURL() string { + if r == nil { + return "" + } + return r.IssuerURL +} +func (r *CreateOrgOIDCConnectionRequest) GetClientID() string { + if r == nil { + return "" + } + return r.ClientID +} +func (r *CreateOrgOIDCConnectionRequest) GetClientSecret() string { + if r == nil { + return "" + } + return r.ClientSecret +} +func (r *CreateOrgOIDCConnectionRequest) GetScopes() *string { + if r == nil { + return nil + } + return r.Scopes +} +func (r *CreateOrgOIDCConnectionRequest) GetRedirectURI() *string { + if r == nil { + return nil + } + return r.RedirectURI +} +func (r *UpdateOrgOIDCConnectionRequest) GetID() string { + if r == nil { + return "" + } + return r.ID +} +func (r *UpdateOrgOIDCConnectionRequest) GetName() *string { + if r == nil { + return nil + } + return r.Name +} +func (r *UpdateOrgOIDCConnectionRequest) GetIssuerURL() *string { + if r == nil { + return nil + } + return r.IssuerURL +} +func (r *UpdateOrgOIDCConnectionRequest) GetClientID() *string { + if r == nil { + return nil + } + return r.ClientID +} +func (r *UpdateOrgOIDCConnectionRequest) GetClientSecret() *string { + if r == nil { + return nil + } + return r.ClientSecret +} +func (r *UpdateOrgOIDCConnectionRequest) GetScopes() *string { + if r == nil { + return nil + } + return r.Scopes +} +func (r *UpdateOrgOIDCConnectionRequest) GetRedirectURI() *string { + if r == nil { + return nil + } + return r.RedirectURI +} +func (r *UpdateOrgOIDCConnectionRequest) GetIsActive() *bool { + if r == nil { + return nil + } + return r.IsActive +} +func (r *OrgOIDCConnectionRequest) GetID() *string { + if r == nil { + return nil + } + return r.ID +} +func (r *OrgOIDCConnectionRequest) GetOrgID() *string { + if r == nil { + return nil + } + return r.OrgID +} +func (r *CreateOrgSAMLConnectionRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *CreateOrgSAMLConnectionRequest) GetName() string { + if r == nil { + return "" + } + return r.Name +} +func (r *CreateOrgSAMLConnectionRequest) GetIdpEntityID() string { + if r == nil { + return "" + } + return r.IdpEntityID +} +func (r *CreateOrgSAMLConnectionRequest) GetIdpSSOURL() string { + if r == nil { + return "" + } + return r.IdpSSOURL +} +func (r *CreateOrgSAMLConnectionRequest) GetIdpCertificate() string { + if r == nil { + return "" + } + return r.IdpCertificate +} +func (r *CreateOrgSAMLConnectionRequest) GetSpEntityID() *string { + if r == nil { + return nil + } + return r.SpEntityID +} +func (r *CreateOrgSAMLConnectionRequest) GetAcsURL() *string { + if r == nil { + return nil + } + return r.AcsURL +} +func (r *CreateOrgSAMLConnectionRequest) GetAttributeMapping() *string { + if r == nil { + return nil + } + return r.AttributeMapping +} +func (r *CreateOrgSAMLConnectionRequest) GetAllowIdpInitiated() *bool { + if r == nil { + return nil + } + return r.AllowIdpInitiated +} +func (r *UpdateOrgSAMLConnectionRequest) GetID() string { + if r == nil { + return "" + } + return r.ID +} +func (r *UpdateOrgSAMLConnectionRequest) GetName() *string { + if r == nil { + return nil + } + return r.Name +} +func (r *UpdateOrgSAMLConnectionRequest) GetIdpEntityID() *string { + if r == nil { + return nil + } + return r.IdpEntityID +} +func (r *UpdateOrgSAMLConnectionRequest) GetIdpSSOURL() *string { + if r == nil { + return nil + } + return r.IdpSSOURL +} +func (r *UpdateOrgSAMLConnectionRequest) GetIdpCertificate() *string { + if r == nil { + return nil + } + return r.IdpCertificate +} +func (r *UpdateOrgSAMLConnectionRequest) GetSpEntityID() *string { + if r == nil { + return nil + } + return r.SpEntityID +} +func (r *UpdateOrgSAMLConnectionRequest) GetAcsURL() *string { + if r == nil { + return nil + } + return r.AcsURL +} +func (r *UpdateOrgSAMLConnectionRequest) GetAttributeMapping() *string { + if r == nil { + return nil + } + return r.AttributeMapping +} +func (r *UpdateOrgSAMLConnectionRequest) GetAllowIdpInitiated() *bool { + if r == nil { + return nil + } + return r.AllowIdpInitiated +} +func (r *UpdateOrgSAMLConnectionRequest) GetIsActive() *bool { + if r == nil { + return nil + } + return r.IsActive +} +func (r *OrgSAMLConnectionRequest) GetID() *string { + if r == nil { + return nil + } + return r.ID +} +func (r *OrgSAMLConnectionRequest) GetOrgID() *string { + if r == nil { + return nil + } + return r.OrgID +} +func (r *CreateScimEndpointRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *ScimEndpointRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *UserOrganizationsRequest) GetUserID() string { + if r == nil { + return "" + } + return r.UserID +} +func (r *RequestOrgDomainRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *RequestOrgDomainRequest) GetDomain() string { + if r == nil { + return "" + } + return r.Domain +} +func (r *VerifyOrgDomainRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *VerifyOrgDomainRequest) GetDomain() string { + if r == nil { + return "" + } + return r.Domain +} +func (r *AddVerifiedOrgDomainRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} +func (r *AddVerifiedOrgDomainRequest) GetDomain() string { + if r == nil { + return "" + } + return r.Domain +} +func (r *DeleteOrgDomainRequest) GetDomain() string { + if r == nil { + return "" + } + return r.Domain +} +func (r *ListOrgDomainsRequest) GetOrgID() string { + if r == nil { + return "" + } + return r.OrgID +} + +// protoPagination converts the SDK pagination input to its proto equivalent, +// tolerating both a nil request and a nil pagination field. +func (r *ListOrganizationsRequest) protoPagination() *authorizerv1.PaginationRequest { + if r == nil || r.Pagination == nil { + return nil + } + return &authorizerv1.PaginationRequest{Page: r.Pagination.Page, Limit: r.Pagination.Limit} +} +func (r *ListOrgMembersRequest) protoPagination() *authorizerv1.PaginationRequest { + if r == nil || r.Pagination == nil { + return nil + } + return &authorizerv1.PaginationRequest{Page: r.Pagination.Page, Limit: r.Pagination.Limit} +} +func (r *UserOrganizationsRequest) protoPagination() *authorizerv1.PaginationRequest { + if r == nil || r.Pagination == nil { + return nil + } + return &authorizerv1.PaginationRequest{Page: r.Pagination.Page, Limit: r.Pagination.Limit} +} +func (r *ListOrgDomainsRequest) protoPagination() *authorizerv1.PaginationRequest { + if r == nil || r.Pagination == nil { + return nil + } + return &authorizerv1.PaginationRequest{Page: r.Pagination.Page, Limit: r.Pagination.Limit} +} diff --git a/admin_client.go b/admin_client.go index 3f854b3..9faf30d 100644 --- a/admin_client.go +++ b/admin_client.go @@ -6,6 +6,8 @@ import ( "fmt" "strings" + "google.golang.org/protobuf/proto" + authorizerv1 "github.com/authorizerdev/authorizer-proto-go/authorizer/v1" ) @@ -92,6 +94,22 @@ type adminMethodSpec struct { // it (e.g. _update_client returns Client, proto UpdateClientResponse{client}). graphqlWrap string + // responseUnwrap is the dual of graphqlWrap, for methods whose out is the + // bare domain object rather than the proto response message: it names the + // single field to unwrap from the REST/gRPC response before unmarshalling. + // Used by the organization / org SSO / SCIM / org domain methods, whose + // hand-written signatures predate the proto (server 2.4.0 added the RPCs) + // and are kept so adding the transports is not a breaking change. + // Leave empty when the response is read whole (a message, or a paginated + // list the domain type already mirrors). + responseUnwrap string + + // restResponse builds the proto response message the REST body is decoded + // into, for methods whose out is a hand-written domain type rather than the + // proto message itself. grpc-gateway emits int64 as a JSON string, which + // doREST only handles for proto.Message targets (via protojson). + restResponse func() proto.Message + // restMethod / restPath; empty restPath means rest-unsupported. restMethod string restPath string @@ -131,6 +149,13 @@ func (c *AuthorizerAdminClient) execute(spec adminMethodSpec, out interface{}) e if spec.restPath == "" { return unsupportedProtocol(spec.name, c.Protocol, spec.supported()) } + if spec.restResponse != nil { + msg := spec.restResponse() + if err := doREST(c.AuthorizerURL, spec.restMethod, spec.restPath, spec.restBody, c.ExtraHeaders, map[string]string{adminSecretHeader: c.AdminSecret}, msg); err != nil { + return err + } + return unwrapProto(msg, spec.responseUnwrap, out) + } return doREST(c.AuthorizerURL, spec.restMethod, spec.restPath, spec.restBody, c.ExtraHeaders, map[string]string{adminSecretHeader: c.AdminSecret}, out) case ProtocolGRPC: @@ -149,7 +174,7 @@ func (c *AuthorizerAdminClient) execute(spec adminMethodSpec, out interface{}) e if err != nil { return err } - return remarshal(resp, out) + return unwrapProto(resp, spec.responseUnwrap, out) default: // ProtocolGraphQL if spec.graphql == nil { @@ -190,3 +215,26 @@ func (c *AuthorizerAdminClient) executeGraphQL(req *GraphQLRequest) ([]byte, err } return uc.ExecuteGraphQL(req, map[string]string{adminSecretHeader: c.AdminSecret}) } + +// unwrapProto converts a proto response message into out, optionally pulling a +// single named field out of it first. Marshalling the proto struct with +// encoding/json (not protojson) is deliberate: it emits int64 as a JSON number, +// which the hand-written domain types can decode. A missing field leaves out at +// its zero value, matching how the graphql path treats an absent field. +func unwrapProto(msg interface{}, field string, out interface{}) error { + if out == nil { + return nil + } + if field == "" { + return remarshal(msg, out) + } + var envelope map[string]json.RawMessage + if err := remarshal(msg, &envelope); err != nil { + return err + } + raw, ok := envelope[field] + if !ok { + return nil + } + return json.Unmarshal(raw, out) +} diff --git a/admin_methods.go b/admin_methods.go index b679824..33d0e81 100644 --- a/admin_methods.go +++ b/admin_methods.go @@ -4,6 +4,8 @@ import ( "context" "net/http" + "google.golang.org/protobuf/proto" + authorizerv1 "github.com/authorizerdev/authorizer-proto-go/authorizer/v1" ) @@ -66,10 +68,14 @@ func (c *AuthorizerAdminClient) AdminLogin(req *authorizerv1.AdminLoginRequest) func (c *AuthorizerAdminClient) AdminLogout() (*authorizerv1.AdminLogoutResponse, error) { var res authorizerv1.AdminLogoutResponse err := c.execute(adminMethodSpec{ - name: "AdminLogout", - restMethod: http.MethodPost, - restPath: "/v1/admin/logout", - restBody: &authorizerv1.AdminLogoutRequest{}, + name: "AdminLogout", + graphql: &GraphQLRequest{ + Query: `mutation adminLogout { _admin_logout { message } }`, + }, + graphqlField: "_admin_logout", + restMethod: http.MethodPost, + restPath: "/v1/admin/logout", + restBody: &authorizerv1.AdminLogoutRequest{}, grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { return cli.AdminLogout(ctx, &authorizerv1.AdminLogoutRequest{}) }, @@ -88,9 +94,13 @@ func (c *AuthorizerAdminClient) AdminLogout() (*authorizerv1.AdminLogoutResponse func (c *AuthorizerAdminClient) AdminSession() (*authorizerv1.AdminSessionResponse, error) { var res authorizerv1.AdminSessionResponse err := c.execute(adminMethodSpec{ - name: "AdminSession", - restMethod: http.MethodGet, - restPath: "/v1/admin/session", + name: "AdminSession", + graphql: &GraphQLRequest{ + Query: `query adminSession { _admin_session { message } }`, + }, + graphqlField: "_admin_session", + restMethod: http.MethodGet, + restPath: "/v1/admin/session", grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { return cli.AdminSession(ctx, &authorizerv1.AdminSessionRequest{}) }, @@ -110,9 +120,14 @@ func (c *AuthorizerAdminClient) AdminSession() (*authorizerv1.AdminSessionRespon func (c *AuthorizerAdminClient) AdminMeta() (*authorizerv1.AdminMetaResponse, error) { var res authorizerv1.AdminMetaResponse err := c.execute(adminMethodSpec{ - name: "AdminMeta", - restMethod: http.MethodGet, - restPath: "/v1/admin/meta", + name: "AdminMeta", + graphql: &GraphQLRequest{ + Query: `query adminMeta { _admin_meta { roles default_roles protected_roles is_multi_factor_auth_service_enabled } }`, + }, + graphqlField: "_admin_meta", + graphqlWrap: "admin_meta", + restMethod: http.MethodGet, + restPath: "/v1/admin/meta", grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { return cli.AdminMeta(ctx, &authorizerv1.AdminMetaRequest{}) }, @@ -673,9 +688,14 @@ func (c *AuthorizerAdminClient) AuditLogs(req *authorizerv1.AuditLogsRequest) (* func (c *AuthorizerAdminClient) FgaGetModel() (*authorizerv1.FgaGetModelResponse, error) { var res authorizerv1.FgaGetModelResponse err := c.execute(adminMethodSpec{ - name: "FgaGetModel", - restMethod: http.MethodGet, - restPath: "/v1/admin/fga/model", + name: "FgaGetModel", + graphql: &GraphQLRequest{ + Query: `query adminFgaGetModel { _fga_get_model { id dsl } }`, + }, + graphqlField: "_fga_get_model", + graphqlWrap: "model", + restMethod: http.MethodGet, + restPath: "/v1/admin/fga/model", grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { return cli.FgaGetModel(ctx, &authorizerv1.FgaGetModelRequest{}) }, @@ -859,10 +879,14 @@ func (c *AuthorizerAdminClient) FgaExpand(req *authorizerv1.FgaExpandRequest) (* func (c *AuthorizerAdminClient) FgaReset() (*authorizerv1.FgaResetResponse, error) { var res authorizerv1.FgaResetResponse err := c.execute(adminMethodSpec{ - name: "FgaReset", - restMethod: http.MethodPost, - restPath: "/v1/admin/fga/reset", - restBody: &authorizerv1.FgaResetRequest{}, + name: "FgaReset", + graphql: &GraphQLRequest{ + Query: `mutation adminFgaReset { _fga_reset { message } }`, + }, + graphqlField: "_fga_reset", + restMethod: http.MethodPost, + restPath: "/v1/admin/fga/reset", + restBody: &authorizerv1.FgaResetRequest{}, grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { return cli.FgaReset(ctx, &authorizerv1.FgaResetRequest{}) }, @@ -1542,18 +1566,6 @@ func (c *AuthorizerAdminClient) GenerateJWTKeys(req *GenerateJWTKeysRequest) (*G // clear unsupported-protocol error. Types mirror the GraphQL schema. // --------------------------------------------------------------------------- -// gqlOnly runs a graphql-only admin operation (no REST path, no gRPC stub). -func (c *AuthorizerAdminClient) gqlOnly(name, query, field string, req, out interface{}) error { - return c.execute(adminMethodSpec{ - name: name, - graphql: &GraphQLRequest{ - Query: query, - Variables: map[string]interface{}{"data": req}, - }, - graphqlField: field, - }, out) -} - // PaginationRequest mirrors the GraphQL PaginationRequest input. type PaginationRequest struct { Limit int64 `json:"limit,omitempty"` @@ -1651,9 +1663,25 @@ type ListOrgMembersRequest struct { // CreateOrganization creates an organization (gql only). func (c *AuthorizerAdminClient) CreateOrganization(req *CreateOrganizationRequest) (*Organization, error) { var res Organization - if err := c.gqlOnly("CreateOrganization", - "mutation createOrganization($data: CreateOrganizationRequest!) { _create_organization(params: $data) { "+adminOrgFields+" } }", - "_create_organization", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "CreateOrganization", + graphql: &GraphQLRequest{ + Query: "mutation createOrganization($data: CreateOrganizationRequest!) { _create_organization(params: $data) { " + adminOrgFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_create_organization", + responseUnwrap: "organization", + restResponse: func() proto.Message { return &authorizerv1.CreateOrganizationResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/create_organization", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.CreateOrganization(ctx, &authorizerv1.CreateOrganizationRequest{ + Name: req.GetName(), DisplayName: req.GetDisplayName(), + }) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1662,9 +1690,25 @@ func (c *AuthorizerAdminClient) CreateOrganization(req *CreateOrganizationReques // UpdateOrganization updates an organization (gql only). func (c *AuthorizerAdminClient) UpdateOrganization(req *UpdateOrganizationRequest) (*Organization, error) { var res Organization - if err := c.gqlOnly("UpdateOrganization", - "mutation updateOrganization($data: UpdateOrganizationRequest!) { _update_organization(params: $data) { "+adminOrgFields+" } }", - "_update_organization", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "UpdateOrganization", + graphql: &GraphQLRequest{ + Query: "mutation updateOrganization($data: UpdateOrganizationRequest!) { _update_organization(params: $data) { " + adminOrgFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_update_organization", + responseUnwrap: "organization", + restResponse: func() proto.Message { return &authorizerv1.UpdateOrganizationResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/update_organization", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.UpdateOrganization(ctx, &authorizerv1.UpdateOrganizationRequest{ + Id: req.GetID(), Name: req.GetName(), DisplayName: req.GetDisplayName(), Enabled: req.GetEnabled(), + }) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1674,9 +1718,22 @@ func (c *AuthorizerAdminClient) UpdateOrganization(req *UpdateOrganizationReques // permanently removes the organization and its memberships/connections. func (c *AuthorizerAdminClient) DeleteOrganization(req *OrganizationRequest) (*Response, error) { var res Response - if err := c.gqlOnly("DeleteOrganization", - `mutation deleteOrganization($data: OrganizationRequest!) { _delete_organization(params: $data) { message } }`, - "_delete_organization", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "DeleteOrganization", + graphql: &GraphQLRequest{ + Query: `mutation deleteOrganization($data: OrganizationRequest!) { _delete_organization(params: $data) { message } }`, + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_delete_organization", + restResponse: func() proto.Message { return &authorizerv1.DeleteOrganizationResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/delete_organization", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.DeleteOrganization(ctx, &authorizerv1.DeleteOrganizationRequest{Id: req.GetID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1685,9 +1742,25 @@ func (c *AuthorizerAdminClient) DeleteOrganization(req *OrganizationRequest) (*R // AddOrgMember adds a user to an organization (gql only). func (c *AuthorizerAdminClient) AddOrgMember(req *AddOrgMemberRequest) (*OrgMember, error) { var res OrgMember - if err := c.gqlOnly("AddOrgMember", - "mutation addOrgMember($data: AddOrgMemberRequest!) { _add_org_member(params: $data) { "+adminOrgMemberFields+" } }", - "_add_org_member", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "AddOrgMember", + graphql: &GraphQLRequest{ + Query: "mutation addOrgMember($data: AddOrgMemberRequest!) { _add_org_member(params: $data) { " + adminOrgMemberFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_add_org_member", + responseUnwrap: "org_member", + restResponse: func() proto.Message { return &authorizerv1.AddOrgMemberResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/add_org_member", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.AddOrgMember(ctx, &authorizerv1.AddOrgMemberRequest{ + OrgId: req.GetOrgID(), UserId: req.GetUserID(), Roles: req.GetRoles(), + }) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1696,9 +1769,22 @@ func (c *AuthorizerAdminClient) AddOrgMember(req *AddOrgMemberRequest) (*OrgMemb // RemoveOrgMember removes a user from an organization (gql only). func (c *AuthorizerAdminClient) RemoveOrgMember(req *RemoveOrgMemberRequest) (*Response, error) { var res Response - if err := c.gqlOnly("RemoveOrgMember", - `mutation removeOrgMember($data: RemoveOrgMemberRequest!) { _remove_org_member(params: $data) { message } }`, - "_remove_org_member", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "RemoveOrgMember", + graphql: &GraphQLRequest{ + Query: `mutation removeOrgMember($data: RemoveOrgMemberRequest!) { _remove_org_member(params: $data) { message } }`, + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_remove_org_member", + restResponse: func() proto.Message { return &authorizerv1.RemoveOrgMemberResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/remove_org_member", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.RemoveOrgMember(ctx, &authorizerv1.RemoveOrgMemberRequest{OrgId: req.GetOrgID(), UserId: req.GetUserID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1707,9 +1793,23 @@ func (c *AuthorizerAdminClient) RemoveOrgMember(req *RemoveOrgMemberRequest) (*R // GetOrganization returns a single organization by id (gql only). func (c *AuthorizerAdminClient) GetOrganization(req *OrganizationRequest) (*Organization, error) { var res Organization - if err := c.gqlOnly("GetOrganization", - "query organization($data: OrganizationRequest!) { _organization(params: $data) { "+adminOrgFields+" } }", - "_organization", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "GetOrganization", + graphql: &GraphQLRequest{ + Query: "query organization($data: OrganizationRequest!) { _organization(params: $data) { " + adminOrgFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_organization", + responseUnwrap: "organization", + restResponse: func() proto.Message { return &authorizerv1.GetOrganizationResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/organization", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.GetOrganization(ctx, &authorizerv1.GetOrganizationRequest{Id: req.GetID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1718,9 +1818,22 @@ func (c *AuthorizerAdminClient) GetOrganization(req *OrganizationRequest) (*Orga // Organizations returns a paginated list of organizations (gql only). func (c *AuthorizerAdminClient) Organizations(req *ListOrganizationsRequest) (*Organizations, error) { var res Organizations - if err := c.gqlOnly("Organizations", - "query organizations($data: ListOrganizationsRequest) { _organizations(params: $data) { "+adminPaginationFields+" organizations { "+adminOrgFields+" } } }", - "_organizations", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "Organizations", + graphql: &GraphQLRequest{ + Query: "query organizations($data: ListOrganizationsRequest) { _organizations(params: $data) { " + adminPaginationFields + " organizations { " + adminOrgFields + " } } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_organizations", + restResponse: func() proto.Message { return &authorizerv1.OrganizationsResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/organizations", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.Organizations(ctx, &authorizerv1.OrganizationsRequest{Pagination: req.protoPagination()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1729,9 +1842,22 @@ func (c *AuthorizerAdminClient) Organizations(req *ListOrganizationsRequest) (*O // OrgMembers returns a paginated list of an organization's members (gql only). func (c *AuthorizerAdminClient) OrgMembers(req *ListOrgMembersRequest) (*OrgMembers, error) { var res OrgMembers - if err := c.gqlOnly("OrgMembers", - "query orgMembers($data: ListOrgMembersRequest!) { _org_members(params: $data) { "+adminPaginationFields+" org_members { "+adminOrgMemberFields+" } } }", - "_org_members", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "OrgMembers", + graphql: &GraphQLRequest{ + Query: "query orgMembers($data: ListOrgMembersRequest!) { _org_members(params: $data) { " + adminPaginationFields + " org_members { " + adminOrgMemberFields + " } } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_org_members", + restResponse: func() proto.Message { return &authorizerv1.OrgMembersResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/org_members", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.OrgMembers(ctx, &authorizerv1.OrgMembersRequest{OrgId: req.GetOrgID(), Pagination: req.protoPagination()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1787,9 +1913,27 @@ type OrgOIDCConnectionRequest struct { // CreateOrgOIDCConnection creates an org OIDC SSO connection (gql only). func (c *AuthorizerAdminClient) CreateOrgOIDCConnection(req *CreateOrgOIDCConnectionRequest) (*OrgOIDCConnection, error) { var res OrgOIDCConnection - if err := c.gqlOnly("CreateOrgOIDCConnection", - "mutation createOrgOidcConnection($data: CreateOrgOIDCConnectionRequest!) { _create_org_oidc_connection(params: $data) { "+adminOrgOIDCConnFields+" } }", - "_create_org_oidc_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "CreateOrgOIDCConnection", + graphql: &GraphQLRequest{ + Query: "mutation createOrgOidcConnection($data: CreateOrgOIDCConnectionRequest!) { _create_org_oidc_connection(params: $data) { " + adminOrgOIDCConnFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_create_org_oidc_connection", + responseUnwrap: "org_oidc_connection", + restResponse: func() proto.Message { return &authorizerv1.CreateOrgOidcConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/create_org_oidc_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.CreateOrgOidcConnection(ctx, &authorizerv1.CreateOrgOidcConnectionRequest{ + OrgId: req.GetOrgID(), Name: req.GetName(), IssuerUrl: req.GetIssuerURL(), + ClientId: req.GetClientID(), ClientSecret: req.GetClientSecret(), + Scopes: req.GetScopes(), RedirectUri: req.GetRedirectURI(), + }) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1798,9 +1942,27 @@ func (c *AuthorizerAdminClient) CreateOrgOIDCConnection(req *CreateOrgOIDCConnec // UpdateOrgOIDCConnection updates an org OIDC SSO connection (gql only). func (c *AuthorizerAdminClient) UpdateOrgOIDCConnection(req *UpdateOrgOIDCConnectionRequest) (*OrgOIDCConnection, error) { var res OrgOIDCConnection - if err := c.gqlOnly("UpdateOrgOIDCConnection", - "mutation updateOrgOidcConnection($data: UpdateOrgOIDCConnectionRequest!) { _update_org_oidc_connection(params: $data) { "+adminOrgOIDCConnFields+" } }", - "_update_org_oidc_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "UpdateOrgOIDCConnection", + graphql: &GraphQLRequest{ + Query: "mutation updateOrgOidcConnection($data: UpdateOrgOIDCConnectionRequest!) { _update_org_oidc_connection(params: $data) { " + adminOrgOIDCConnFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_update_org_oidc_connection", + responseUnwrap: "org_oidc_connection", + restResponse: func() proto.Message { return &authorizerv1.UpdateOrgOidcConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/update_org_oidc_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.UpdateOrgOidcConnection(ctx, &authorizerv1.UpdateOrgOidcConnectionRequest{ + Id: req.GetID(), Name: req.GetName(), IssuerUrl: req.GetIssuerURL(), + ClientId: req.GetClientID(), ClientSecret: req.GetClientSecret(), + Scopes: req.GetScopes(), RedirectUri: req.GetRedirectURI(), IsActive: req.GetIsActive(), + }) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1810,9 +1972,22 @@ func (c *AuthorizerAdminClient) UpdateOrgOIDCConnection(req *UpdateOrgOIDCConnec // DESTRUCTIVE: SSO logins through this connection stop working immediately. func (c *AuthorizerAdminClient) DeleteOrgOIDCConnection(req *OrgOIDCConnectionRequest) (*Response, error) { var res Response - if err := c.gqlOnly("DeleteOrgOIDCConnection", - `mutation deleteOrgOidcConnection($data: OrgOIDCConnectionRequest!) { _delete_org_oidc_connection(params: $data) { message } }`, - "_delete_org_oidc_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "DeleteOrgOIDCConnection", + graphql: &GraphQLRequest{ + Query: `mutation deleteOrgOidcConnection($data: OrgOIDCConnectionRequest!) { _delete_org_oidc_connection(params: $data) { message } }`, + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_delete_org_oidc_connection", + restResponse: func() proto.Message { return &authorizerv1.DeleteOrgOidcConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/delete_org_oidc_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.DeleteOrgOidcConnection(ctx, &authorizerv1.DeleteOrgOidcConnectionRequest{Id: req.GetID(), OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1822,9 +1997,23 @@ func (c *AuthorizerAdminClient) DeleteOrgOIDCConnection(req *OrgOIDCConnectionRe // (gql only). func (c *AuthorizerAdminClient) GetOrgOIDCConnection(req *OrgOIDCConnectionRequest) (*OrgOIDCConnection, error) { var res OrgOIDCConnection - if err := c.gqlOnly("GetOrgOIDCConnection", - "query orgOidcConnection($data: OrgOIDCConnectionRequest!) { _org_oidc_connection(params: $data) { "+adminOrgOIDCConnFields+" } }", - "_org_oidc_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "GetOrgOIDCConnection", + graphql: &GraphQLRequest{ + Query: "query orgOidcConnection($data: OrgOIDCConnectionRequest!) { _org_oidc_connection(params: $data) { " + adminOrgOIDCConnFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_org_oidc_connection", + responseUnwrap: "org_oidc_connection", + restResponse: func() proto.Message { return &authorizerv1.GetOrgOidcConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/org_oidc_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.GetOrgOidcConnection(ctx, &authorizerv1.GetOrgOidcConnectionRequest{Id: req.GetID(), OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1886,9 +2075,28 @@ type OrgSAMLConnectionRequest struct { // CreateOrgSAMLConnection creates an org SAML SSO connection (gql only). func (c *AuthorizerAdminClient) CreateOrgSAMLConnection(req *CreateOrgSAMLConnectionRequest) (*OrgSAMLConnection, error) { var res OrgSAMLConnection - if err := c.gqlOnly("CreateOrgSAMLConnection", - "mutation createOrgSamlConnection($data: CreateOrgSAMLConnectionRequest!) { _create_org_saml_connection(params: $data) { "+adminOrgSAMLConnFields+" } }", - "_create_org_saml_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "CreateOrgSAMLConnection", + graphql: &GraphQLRequest{ + Query: "mutation createOrgSamlConnection($data: CreateOrgSAMLConnectionRequest!) { _create_org_saml_connection(params: $data) { " + adminOrgSAMLConnFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_create_org_saml_connection", + responseUnwrap: "org_saml_connection", + restResponse: func() proto.Message { return &authorizerv1.CreateOrgSamlConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/create_org_saml_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.CreateOrgSamlConnection(ctx, &authorizerv1.CreateOrgSamlConnectionRequest{ + OrgId: req.GetOrgID(), Name: req.GetName(), IdpEntityId: req.GetIdpEntityID(), + IdpSsoUrl: req.GetIdpSSOURL(), IdpCertificate: req.GetIdpCertificate(), + SpEntityId: req.GetSpEntityID(), AcsUrl: req.GetAcsURL(), + AttributeMapping: req.GetAttributeMapping(), AllowIdpInitiated: req.GetAllowIdpInitiated(), + }) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1897,9 +2105,29 @@ func (c *AuthorizerAdminClient) CreateOrgSAMLConnection(req *CreateOrgSAMLConnec // UpdateOrgSAMLConnection updates an org SAML SSO connection (gql only). func (c *AuthorizerAdminClient) UpdateOrgSAMLConnection(req *UpdateOrgSAMLConnectionRequest) (*OrgSAMLConnection, error) { var res OrgSAMLConnection - if err := c.gqlOnly("UpdateOrgSAMLConnection", - "mutation updateOrgSamlConnection($data: UpdateOrgSAMLConnectionRequest!) { _update_org_saml_connection(params: $data) { "+adminOrgSAMLConnFields+" } }", - "_update_org_saml_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "UpdateOrgSAMLConnection", + graphql: &GraphQLRequest{ + Query: "mutation updateOrgSamlConnection($data: UpdateOrgSAMLConnectionRequest!) { _update_org_saml_connection(params: $data) { " + adminOrgSAMLConnFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_update_org_saml_connection", + responseUnwrap: "org_saml_connection", + restResponse: func() proto.Message { return &authorizerv1.UpdateOrgSamlConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/update_org_saml_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.UpdateOrgSamlConnection(ctx, &authorizerv1.UpdateOrgSamlConnectionRequest{ + Id: req.GetID(), Name: req.GetName(), IdpEntityId: req.GetIdpEntityID(), + IdpSsoUrl: req.GetIdpSSOURL(), IdpCertificate: req.GetIdpCertificate(), + SpEntityId: req.GetSpEntityID(), AcsUrl: req.GetAcsURL(), + AttributeMapping: req.GetAttributeMapping(), AllowIdpInitiated: req.GetAllowIdpInitiated(), + IsActive: req.GetIsActive(), + }) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1909,9 +2137,22 @@ func (c *AuthorizerAdminClient) UpdateOrgSAMLConnection(req *UpdateOrgSAMLConnec // DESTRUCTIVE: SSO logins through this connection stop working immediately. func (c *AuthorizerAdminClient) DeleteOrgSAMLConnection(req *OrgSAMLConnectionRequest) (*Response, error) { var res Response - if err := c.gqlOnly("DeleteOrgSAMLConnection", - `mutation deleteOrgSamlConnection($data: OrgSAMLConnectionRequest!) { _delete_org_saml_connection(params: $data) { message } }`, - "_delete_org_saml_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "DeleteOrgSAMLConnection", + graphql: &GraphQLRequest{ + Query: `mutation deleteOrgSamlConnection($data: OrgSAMLConnectionRequest!) { _delete_org_saml_connection(params: $data) { message } }`, + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_delete_org_saml_connection", + restResponse: func() proto.Message { return &authorizerv1.DeleteOrgSamlConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/delete_org_saml_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.DeleteOrgSamlConnection(ctx, &authorizerv1.DeleteOrgSamlConnectionRequest{Id: req.GetID(), OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1921,9 +2162,23 @@ func (c *AuthorizerAdminClient) DeleteOrgSAMLConnection(req *OrgSAMLConnectionRe // (gql only). func (c *AuthorizerAdminClient) GetOrgSAMLConnection(req *OrgSAMLConnectionRequest) (*OrgSAMLConnection, error) { var res OrgSAMLConnection - if err := c.gqlOnly("GetOrgSAMLConnection", - "query orgSamlConnection($data: OrgSAMLConnectionRequest!) { _org_saml_connection(params: $data) { "+adminOrgSAMLConnFields+" } }", - "_org_saml_connection", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "GetOrgSAMLConnection", + graphql: &GraphQLRequest{ + Query: "query orgSamlConnection($data: OrgSAMLConnectionRequest!) { _org_saml_connection(params: $data) { " + adminOrgSAMLConnFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_org_saml_connection", + responseUnwrap: "org_saml_connection", + restResponse: func() proto.Message { return &authorizerv1.GetOrgSamlConnectionResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/org_saml_connection", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.GetOrgSamlConnection(ctx, &authorizerv1.GetOrgSamlConnectionRequest{Id: req.GetID(), OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1962,9 +2217,22 @@ const scimEndpointResponseFragment = "scim_endpoint { " + adminScimEndpointField // CreateScimEndpoint provisions a SCIM endpoint for an organization (gql only). func (c *AuthorizerAdminClient) CreateScimEndpoint(req *CreateScimEndpointRequest) (*CreateScimEndpointResponse, error) { var res CreateScimEndpointResponse - if err := c.gqlOnly("CreateScimEndpoint", - "mutation createScimEndpoint($data: CreateScimEndpointRequest!) { _create_scim_endpoint(params: $data) { "+scimEndpointResponseFragment+" } }", - "_create_scim_endpoint", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "CreateScimEndpoint", + graphql: &GraphQLRequest{ + Query: "mutation createScimEndpoint($data: CreateScimEndpointRequest!) { _create_scim_endpoint(params: $data) { " + scimEndpointResponseFragment + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_create_scim_endpoint", + restResponse: func() proto.Message { return &authorizerv1.CreateScimEndpointResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/create_scim_endpoint", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.CreateScimEndpoint(ctx, &authorizerv1.CreateScimEndpointRequest{OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1974,9 +2242,22 @@ func (c *AuthorizerAdminClient) CreateScimEndpoint(req *CreateScimEndpointReques // token is returned ONCE; the old token stops validating. func (c *AuthorizerAdminClient) RotateScimToken(req *ScimEndpointRequest) (*CreateScimEndpointResponse, error) { var res CreateScimEndpointResponse - if err := c.gqlOnly("RotateScimToken", - "mutation rotateScimToken($data: ScimEndpointRequest!) { _rotate_scim_token(params: $data) { "+scimEndpointResponseFragment+" } }", - "_rotate_scim_token", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "RotateScimToken", + graphql: &GraphQLRequest{ + Query: "mutation rotateScimToken($data: ScimEndpointRequest!) { _rotate_scim_token(params: $data) { " + scimEndpointResponseFragment + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_rotate_scim_token", + restResponse: func() proto.Message { return &authorizerv1.CreateScimEndpointResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/rotate_scim_token", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.RotateScimToken(ctx, &authorizerv1.RotateScimTokenRequest{OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1986,9 +2267,22 @@ func (c *AuthorizerAdminClient) RotateScimToken(req *ScimEndpointRequest) (*Crea // DESTRUCTIVE: the IdP's provisioning token stops working immediately. func (c *AuthorizerAdminClient) DeleteScimEndpoint(req *ScimEndpointRequest) (*Response, error) { var res Response - if err := c.gqlOnly("DeleteScimEndpoint", - `mutation deleteScimEndpoint($data: ScimEndpointRequest!) { _delete_scim_endpoint(params: $data) { message } }`, - "_delete_scim_endpoint", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "DeleteScimEndpoint", + graphql: &GraphQLRequest{ + Query: `mutation deleteScimEndpoint($data: ScimEndpointRequest!) { _delete_scim_endpoint(params: $data) { message } }`, + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_delete_scim_endpoint", + restResponse: func() proto.Message { return &authorizerv1.DeleteScimEndpointResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/delete_scim_endpoint", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.DeleteScimEndpoint(ctx, &authorizerv1.DeleteScimEndpointRequest{OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -1998,9 +2292,23 @@ func (c *AuthorizerAdminClient) DeleteScimEndpoint(req *ScimEndpointRequest) (*R // bearer token is never returned. func (c *AuthorizerAdminClient) GetScimEndpoint(req *ScimEndpointRequest) (*ScimEndpoint, error) { var res ScimEndpoint - if err := c.gqlOnly("GetScimEndpoint", - "query scimEndpoint($data: ScimEndpointRequest!) { _scim_endpoint(params: $data) { "+adminScimEndpointFields+" } }", - "_scim_endpoint", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "GetScimEndpoint", + graphql: &GraphQLRequest{ + Query: "query scimEndpoint($data: ScimEndpointRequest!) { _scim_endpoint(params: $data) { " + adminScimEndpointFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_scim_endpoint", + responseUnwrap: "scim_endpoint", + restResponse: func() proto.Message { return &authorizerv1.GetScimEndpointResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/scim_endpoint", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.GetScimEndpoint(ctx, &authorizerv1.GetScimEndpointRequest{OrgId: req.GetOrgID()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -2035,9 +2343,22 @@ const adminUserOrgFields = `organization { ` + adminOrgFields + ` } roles` // roles held per org (gql only). func (c *AuthorizerAdminClient) UserOrganizations(req *UserOrganizationsRequest) (*UserOrganizations, error) { var res UserOrganizations - if err := c.gqlOnly("UserOrganizations", - "query userOrganizations($data: UserOrganizationsRequest!) { _user_organizations(params: $data) { "+adminPaginationFields+" user_organizations { "+adminUserOrgFields+" } } }", - "_user_organizations", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "UserOrganizations", + graphql: &GraphQLRequest{ + Query: "query userOrganizations($data: UserOrganizationsRequest!) { _user_organizations(params: $data) { " + adminPaginationFields + " user_organizations { " + adminUserOrgFields + " } } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_user_organizations", + restResponse: func() proto.Message { return &authorizerv1.UserOrganizationsResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/user_organizations", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.UserOrganizations(ctx, &authorizerv1.UserOrganizationsRequest{UserId: req.GetUserID(), Pagination: req.protoPagination()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -2112,9 +2433,23 @@ const adminOrgDomainFields = `domain org_id verified_at created_at updated_at` // the domain (gql only). func (c *AuthorizerAdminClient) RequestOrgDomain(req *RequestOrgDomainRequest) (*OrgDomainChallenge, error) { var res OrgDomainChallenge - if err := c.gqlOnly("RequestOrgDomain", - `mutation requestOrgDomain($data: RequestOrgDomainRequest!) { _request_org_domain(params: $data) { domain record_type record_name record_value } }`, - "_request_org_domain", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "RequestOrgDomain", + graphql: &GraphQLRequest{ + Query: `mutation requestOrgDomain($data: RequestOrgDomainRequest!) { _request_org_domain(params: $data) { domain record_type record_name record_value } }`, + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_request_org_domain", + responseUnwrap: "challenge", + restResponse: func() proto.Message { return &authorizerv1.RequestOrgDomainResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/request_org_domain", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.RequestOrgDomain(ctx, &authorizerv1.RequestOrgDomainRequest{OrgId: req.GetOrgID(), Domain: req.GetDomain()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -2124,9 +2459,23 @@ func (c *AuthorizerAdminClient) RequestOrgDomain(req *RequestOrgDomainRequest) ( // domain (gql only). func (c *AuthorizerAdminClient) VerifyOrgDomain(req *VerifyOrgDomainRequest) (*OrgDomain, error) { var res OrgDomain - if err := c.gqlOnly("VerifyOrgDomain", - "mutation verifyOrgDomain($data: VerifyOrgDomainRequest!) { _verify_org_domain(params: $data) { "+adminOrgDomainFields+" } }", - "_verify_org_domain", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "VerifyOrgDomain", + graphql: &GraphQLRequest{ + Query: "mutation verifyOrgDomain($data: VerifyOrgDomainRequest!) { _verify_org_domain(params: $data) { " + adminOrgDomainFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_verify_org_domain", + responseUnwrap: "org_domain", + restResponse: func() proto.Message { return &authorizerv1.VerifyOrgDomainResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/verify_org_domain", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.VerifyOrgDomain(ctx, &authorizerv1.VerifyOrgDomainRequest{OrgId: req.GetOrgID(), Domain: req.GetDomain()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -2136,9 +2485,23 @@ func (c *AuthorizerAdminClient) VerifyOrgDomain(req *VerifyOrgDomainRequest) (*O // TXT challenge. Super-admin only (gql only). func (c *AuthorizerAdminClient) AddVerifiedOrgDomain(req *AddVerifiedOrgDomainRequest) (*OrgDomain, error) { var res OrgDomain - if err := c.gqlOnly("AddVerifiedOrgDomain", - "mutation addVerifiedOrgDomain($data: AddVerifiedOrgDomainRequest!) { _add_verified_org_domain(params: $data) { "+adminOrgDomainFields+" } }", - "_add_verified_org_domain", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "AddVerifiedOrgDomain", + graphql: &GraphQLRequest{ + Query: "mutation addVerifiedOrgDomain($data: AddVerifiedOrgDomainRequest!) { _add_verified_org_domain(params: $data) { " + adminOrgDomainFields + " } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_add_verified_org_domain", + responseUnwrap: "org_domain", + restResponse: func() proto.Message { return &authorizerv1.AddVerifiedOrgDomainResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/add_verified_org_domain", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.AddVerifiedOrgDomain(ctx, &authorizerv1.AddVerifiedOrgDomainRequest{OrgId: req.GetOrgID(), Domain: req.GetDomain()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -2148,9 +2511,22 @@ func (c *AuthorizerAdminClient) AddVerifiedOrgDomain(req *AddVerifiedOrgDomainRe // relying on this domain for home-realm discovery stop resolving to the org. func (c *AuthorizerAdminClient) DeleteOrgDomain(req *DeleteOrgDomainRequest) (*Response, error) { var res Response - if err := c.gqlOnly("DeleteOrgDomain", - `mutation deleteOrgDomain($data: DeleteOrgDomainRequest!) { _delete_org_domain(params: $data) { message } }`, - "_delete_org_domain", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "DeleteOrgDomain", + graphql: &GraphQLRequest{ + Query: `mutation deleteOrgDomain($data: DeleteOrgDomainRequest!) { _delete_org_domain(params: $data) { message } }`, + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_delete_org_domain", + restResponse: func() proto.Message { return &authorizerv1.DeleteOrgDomainResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/delete_org_domain", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.DeleteOrgDomain(ctx, &authorizerv1.DeleteOrgDomainRequest{Domain: req.GetDomain()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil @@ -2159,9 +2535,22 @@ func (c *AuthorizerAdminClient) DeleteOrgDomain(req *DeleteOrgDomainRequest) (*R // OrgDomains returns an organization's verified domains (gql only). func (c *AuthorizerAdminClient) OrgDomains(req *ListOrgDomainsRequest) (*OrgDomains, error) { var res OrgDomains - if err := c.gqlOnly("OrgDomains", - "query orgDomains($data: ListOrgDomainsRequest!) { _org_domains(params: $data) { "+adminPaginationFields+" org_domains { "+adminOrgDomainFields+" } } }", - "_org_domains", req, &res); err != nil { + err := c.execute(adminMethodSpec{ + name: "OrgDomains", + graphql: &GraphQLRequest{ + Query: "query orgDomains($data: ListOrgDomainsRequest!) { _org_domains(params: $data) { " + adminPaginationFields + " org_domains { " + adminOrgDomainFields + " } } }", + Variables: map[string]interface{}{"data": req}, + }, + graphqlField: "_org_domains", + restResponse: func() proto.Message { return &authorizerv1.OrgDomainsResponse{} }, + restMethod: http.MethodPost, + restPath: "/v1/admin/org_domains", + restBody: req, + grpcCall: func(ctx context.Context, cli authorizerv1.AuthorizerAdminServiceClient) (interface{}, error) { + return cli.OrgDomains(ctx, &authorizerv1.OrgDomainsRequest{OrgId: req.GetOrgID(), Pagination: req.protoPagination()}) + }, + }, &res) + if err != nil { return nil, err } return &res, nil diff --git a/go.mod b/go.mod index ff03fe8..687dd70 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/authorizerdev/authorizer-go/v2 go 1.25.5 require ( - github.com/authorizerdev/authorizer-proto-go v0.1.0 + github.com/authorizerdev/authorizer-proto-go v0.2.0-rc.0 google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 ) diff --git a/go.sum b/go.sum index c765eec..88c970c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 h1:s6hzCXtND/ICdGPTMGk7C+/BFlr2Jg5GyH0NKf4XGXg= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= -github.com/authorizerdev/authorizer-proto-go v0.1.0 h1:oLGE2OuwCnE6Yr1tRt3fL0zh7L/HpPQfoeS4pgxszlQ= -github.com/authorizerdev/authorizer-proto-go v0.1.0/go.mod h1:cVUPv4XVeH3YeoFjfnl+ug/KlUinrGOAUZu3E+sjjHs= +github.com/authorizerdev/authorizer-proto-go v0.2.0-rc.0 h1:PQGjo4yfxU4V4NXOJj1OjbLKNxRpf3ih2eCdTMmUEkY= +github.com/authorizerdev/authorizer-proto-go v0.2.0-rc.0/go.mod h1:cVUPv4XVeH3YeoFjfnl+ug/KlUinrGOAUZu3E+sjjHs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= diff --git a/test/admin_test.go b/test/admin_test.go index 6807e15..3b76b5d 100644 --- a/test/admin_test.go +++ b/test/admin_test.go @@ -66,31 +66,34 @@ func TestAdminUsersAcrossProtocols(t *testing.T) { } } -// TestAdminMetaProtocolAvailability verifies AdminMeta works over rest+grpc and -// returns a clear error over graphql (which has no _admin_meta-shaped op here). +// TestAdminMetaProtocolAvailability verifies AdminMeta works over every +// protocol. It was rest+grpc-only in the SDK despite `_admin_meta` existing on +// the server; the SDK simply carried no query for it. func TestAdminMetaProtocolAvailability(t *testing.T) { - // rest + grpc: supported - for _, p := range []authorizer.Protocol{authorizer.ProtocolREST, authorizer.ProtocolGRPC} { + for _, p := range []authorizer.Protocol{authorizer.ProtocolGraphQL, authorizer.ProtocolREST, authorizer.ProtocolGRPC} { c := adminClient(t, p) - if _, err := c.AdminMeta(); err != nil { + res, err := c.AdminMeta() + if err != nil { t.Fatalf("[%s] AdminMeta failed: %v", p, err) } - } - - // graphql: unsupported → clear error, no network 404 - c := adminClient(t, authorizer.ProtocolGraphQL) - _, err := c.AdminMeta() - if err == nil { - t.Fatal("expected AdminMeta to error over graphql") - } - if !strings.Contains(err.Error(), "not available over graphql") { - t.Errorf("expected clear unsupported-protocol error, got %v", err) + // The proto response nests the payload under admin_meta while the + // GraphQL op returns it directly, so a missing graphqlWrap surfaces + // here as a zero-valued response rather than an error. + if res.GetAdminMeta() == nil || len(res.GetAdminMeta().GetRoles()) == 0 { + t.Errorf("[%s] AdminMeta returned %+v, want roles populated", p, res) + } } } // TestAdminGqlOnlyExtras verifies the gql-only methods error over rest+grpc. // This needs no live server: the unsupported-protocol error fires before any // network call. +// +// Only three admin operations remain graphql-only: _admin_signup, _update_env +// and _generate_jwt_keys have no proto RPC. Organizations, org members, org +// domains, org OIDC/SAML connections and SCIM endpoints DID once belong here, +// and gained RPCs plus REST bindings in server 2.4.0 -- they are now exercised +// over every protocol by TestAdminOrgSurfaceAcrossProtocols instead. func TestAdminGqlOnlyExtras(t *testing.T) { for _, p := range []authorizer.Protocol{authorizer.ProtocolREST, authorizer.ProtocolGRPC} { c := adminClient(t, p) @@ -99,32 +102,8 @@ func TestAdminGqlOnlyExtras(t *testing.T) { _, err := c.GenerateJWTKeys(&authorizer.GenerateJWTKeysRequest{Type: "HS256"}) return err }, - "CreateOrganization": func() error { - _, err := c.CreateOrganization(&authorizer.CreateOrganizationRequest{Name: "acme"}) - return err - }, - "Organizations": func() error { - _, err := c.Organizations(&authorizer.ListOrganizationsRequest{}) - return err - }, - "AddOrgMember": func() error { - _, err := c.AddOrgMember(&authorizer.AddOrgMemberRequest{OrgID: "o1", UserID: "u1"}) - return err - }, - "CreateOrgOIDCConnection": func() error { - _, err := c.CreateOrgOIDCConnection(&authorizer.CreateOrgOIDCConnectionRequest{OrgID: "o1"}) - return err - }, - "GetOrgSAMLConnection": func() error { - _, err := c.GetOrgSAMLConnection(&authorizer.OrgSAMLConnectionRequest{OrgID: authorizer.NewStringRef("o1")}) - return err - }, - "CreateScimEndpoint": func() error { - _, err := c.CreateScimEndpoint(&authorizer.CreateScimEndpointRequest{OrgID: "o1"}) - return err - }, - "RotateScimToken": func() error { - _, err := c.RotateScimToken(&authorizer.ScimEndpointRequest{OrgID: "o1"}) + "UpdateEnv": func() error { + _, err := c.UpdateEnv(&authorizer.UpdateEnvRequest{}) return err }, }