client/client.go (109 lines of code) (raw):
package client
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/checkaccess-v2-go-sdk/client/internal/token"
)
// this asserts that &remotePDPClient{} would always implement RemotePDPClient
var _ RemotePDPClient = &remotePDPClient{}
// RemotePDPClient represents the Microsoft Remote PDP API Spec
type RemotePDPClient interface {
CheckAccess(context.Context, AuthorizationRequest) (*AuthorizationDecisionResponse, error)
CreateAuthorizationRequest(string, []string, string) (*AuthorizationRequest, error)
}
// remotePDPClient implements RemotePDPClient
type remotePDPClient struct {
endpoint string
pipeline runtime.Pipeline
}
// NewRemotePDPClient returns an implementation of RemotePDPClient
// endpoint - the fqdn of the regional specific endpoint of PDP
// scope - the oauth scope required by the PDP server
// cred - the credential of the client to call the PDP server
// ClientOptions - the optional settings for a client's pipeline.
func NewRemotePDPClient(endpoint, scope string, cred azcore.TokenCredential, clientOptions *azcore.ClientOptions) (*remotePDPClient, error) {
if strings.TrimSpace(endpoint) == "" {
return nil, fmt.Errorf("endpoint: %s is not valid, need a valid endpoint in creating client", endpoint)
}
if strings.TrimSpace(scope) == "" {
return nil, fmt.Errorf("scope: %s is not valid, need a valid scope in creating client", scope)
}
if cred == nil {
return nil, fmt.Errorf("need TokenCredential in creating client")
}
authPolicy := runtime.NewBearerTokenPolicy(cred, []string{scope}, nil)
pipeline := runtime.NewPipeline(
modulename,
version,
runtime.PipelineOptions{
PerCall: []policy.Policy{},
PerRetry: []policy.Policy{authPolicy},
},
clientOptions,
)
return &remotePDPClient{endpoint, pipeline}, nil
}
// CheckAccess sends an Authorization query to the PDP server specified in the client
// ctx - the context to propagate
// authzReq - the actual AuthorizationRequest
func (r *remotePDPClient) CheckAccess(ctx context.Context, authzReq AuthorizationRequest) (*AuthorizationDecisionResponse, error) {
req, err := runtime.NewRequest(ctx, http.MethodPost, r.endpoint)
if err != nil {
return nil, err
}
if err := runtime.MarshalAsJSON(req, authzReq); err != nil {
return nil, err
}
res, err := r.pipeline.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, newCheckAccessError(res)
}
var accessDecision AuthorizationDecisionResponse
if err := runtime.UnmarshalAsJSON(res, &accessDecision); err != nil {
return nil, err
}
return &accessDecision, nil
}
// newCheckAccessError returns an error when non HTTP 200 response is returned.
func newCheckAccessError(r *http.Response) error {
payload, err := runtime.Payload(r)
if err != nil {
return err
}
var checkAccessError CheckAccessErrorResponse
err = json.Unmarshal(payload, &checkAccessError)
if err != nil {
return err
}
return &azcore.ResponseError{
StatusCode: r.StatusCode,
RawResponse: r,
ErrorCode: fmt.Sprint(checkAccessError.StatusCode),
}
}
// CreateAuthorizationRequest creates an AuthorizationRequest object
func (r *remotePDPClient) CreateAuthorizationRequest(resourceId string, actions []string, jwtToken string) (*AuthorizationRequest, error) {
if strings.TrimSpace(jwtToken) == "" {
return nil, fmt.Errorf("need token in creating AuthorizationRequest")
}
tokenClaims, err := token.ExtractClaims(jwtToken)
if err != nil {
return nil, fmt.Errorf("error while parse the token, err: %v", err)
}
subjectAttributes := SubjectAttributes{}
subjectAttributes.ObjectId = tokenClaims.ObjectId
if tokenClaims.ClaimNames != nil && len(tokenClaims.Groups) == 0 {
subjectAttributes.ClaimName = GroupExpansion
} else if tokenClaims.ClaimNames == nil && len(tokenClaims.Groups) > 0 {
subjectAttributes.Groups = tokenClaims.Groups
}
actionInfos := []ActionInfo{}
for _, action := range actions {
actionInfos = append(actionInfos, ActionInfo{Id: action})
}
return &AuthorizationRequest{
Subject: SubjectInfo{
Attributes: subjectAttributes,
},
Actions: actionInfos,
Resource: ResourceInfo{
Id: resourceId,
},
}, nil
}