Skip to content
This repository has been archived by the owner on Dec 11, 2023. It is now read-only.

Commit

Permalink
Merge pull request #1 from G-PORTAL/more-dynamic-auth-options
Browse files Browse the repository at this point in the history
Add more dynamic options to overwrite client settings
  • Loading branch information
BirknerAlex authored Feb 7, 2023
2 parents 5391e0a + 122eae1 commit e8b8855
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 84 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import (
authv1 "buf.build/gen/go/gportal/gportal-cloud/protocolbuffers/go/gpcloud/api/auth/v1"
cloudv1 "buf.build/gen/go/gportal/gportal-cloud/protocolbuffers/go/gpcloud/api/cloud/v1"
"github.com/G-PORTAL/gpcloud-go/pkg/gpcloud/client"
"github.com/G-PORTAL/gpcloud-go/pkg/gpcloud/client/auth"
)

func main() {
conn, err := client.NewClient(
// For getting your own client ID and client Secret please ask support
client.AuthOptions{
&auth.ProviderKeycloakUserPassword{
ClientID: "my-custom-client-id",
ClientSecret: "my-custom-client-secret",
Username: "example@gpcloud.customer",
Expand All @@ -49,4 +50,5 @@ func main() {
log.Println("Project ID: ", project.Id)
}
}

```
83 changes: 12 additions & 71 deletions pkg/gpcloud/client/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,29 @@ package client
import (
"context"
"fmt"
"time"

"github.com/Nerzal/gocloak/v12"
)

const defaultKeycloakHostname = "https://auth.g-portal.com/auth"
const defaultKeycloakRealm = "master"

type GPCloudAuth struct {
jwtToken *gocloak.JWT
gocloakClient *gocloak.GoCloak
expires time.Time
authOpts *AuthOptions
type AuthProviderOption interface {
GetToken(context.Context) (string, error)
}

type AuthOptions struct {
ClientID string
ClientSecret string
Username string
Password string
Realm *string
Hostname *string
type AuthOption struct {
Provider *AuthProviderOption
}

func (authOptions *AuthOptions) GetRealm() string {
if authOptions.Realm == nil {
return defaultKeycloakRealm
}
return *authOptions.Realm
}
func (authOptions *AuthOptions) GetHostname() string {
if authOptions.Hostname == nil {
return defaultKeycloakHostname
func (a *AuthOption) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
if (a.Provider == nil) || (*a.Provider == nil) {
return nil, fmt.Errorf("no provider set")
}
return *authOptions.Hostname
}

func (a *GPCloudAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
if a.expires.Before(time.Now()) {
if err := a.refresh(); err != nil {
if err2 := a.login(); err2 != nil {
return nil, err2
}
return nil, err
}
token, err := (*a.Provider).GetToken(ctx)
if err != nil {
return nil, err
}
return map[string]string{
"authorization": fmt.Sprintf("Bearer %s", a.jwtToken.AccessToken),
"authorization": fmt.Sprintf("Bearer %s", token),
}, nil
}

func (a *GPCloudAuth) RequireTransportSecurity() bool {
func (a *AuthOption) RequireTransportSecurity() bool {
return true
}

func NewAuth(opts *AuthOptions) (*GPCloudAuth, error) {
auth := &GPCloudAuth{
authOpts: opts,
}
auth.gocloakClient = gocloak.NewClient(opts.GetHostname())
if err := auth.login(); err != nil {
return nil, err
}
return auth, nil
}

func (a *GPCloudAuth) login() error {
token, err := a.gocloakClient.Login(context.Background(), a.authOpts.ClientID, a.authOpts.ClientSecret, a.authOpts.GetRealm(), a.authOpts.Username, a.authOpts.Password)
if err != nil {
return err
}
a.jwtToken = token
a.expires = time.Now().Add(time.Duration(token.ExpiresIn-10) * time.Second)
return nil
}

func (a *GPCloudAuth) refresh() error {
token, err := a.gocloakClient.RefreshToken(context.Background(), a.jwtToken.RefreshToken, a.authOpts.ClientID, a.authOpts.ClientSecret, a.authOpts.GetRealm())
if err != nil {
return err
}
a.jwtToken = token
a.expires = time.Now().Add(time.Duration(token.ExpiresIn-10) * time.Second)
return nil
}
4 changes: 4 additions & 0 deletions pkg/gpcloud/client/auth/auth_keycloak.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package auth

const hostnameDefault = "https://auth.g-portal.com/auth"
const realmDefault = "master"
57 changes: 57 additions & 0 deletions pkg/gpcloud/client/auth/auth_keycloak_client_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* This authentication method is not fully implemented (yet)
but will be used as long life access token replacement later on */

package auth

import (
"context"
"time"

"github.com/Nerzal/gocloak/v12"
)

type ProviderKeycloakClientAuth struct {
ClientID string
ClientSecret string
Hostname *string
Realm *string

tokenData *struct {
Token *gocloak.JWT
Expires time.Time
}
}

func (p *ProviderKeycloakClientAuth) refresh() error {
hostname := hostnameDefault
if p.Hostname != nil {
hostname = *p.Hostname
}
realm := realmDefault
if p.Realm != nil {
realm = *p.Realm
}
token, err := gocloak.NewClient(hostname).GetToken(context.Background(), realm, gocloak.TokenOptions{
ClientID: &p.ClientID,
ClientSecret: &p.ClientSecret,
GrantType: gocloak.StringP("client_credentials"),
})
if err != nil {
p.tokenData = nil
return err
}
p.tokenData = &struct {
Token *gocloak.JWT
Expires time.Time
}{Token: token, Expires: time.Now().Add(time.Duration(token.ExpiresIn-10) * time.Second)}
return nil
}

func (p *ProviderKeycloakClientAuth) GetToken(ctx context.Context) (string, error) {
if p.tokenData == nil || p.tokenData.Expires.Before(time.Now()) {
if err := p.refresh(); err != nil {
return "", err
}
}
return p.tokenData.Token.AccessToken, nil
}
64 changes: 64 additions & 0 deletions pkg/gpcloud/client/auth/auth_keycloak_username_password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package auth

import (
"context"
"time"

"github.com/Nerzal/gocloak/v12"
)

type ProviderKeycloakUserPassword struct {
ClientID string
ClientSecret string
Username string
Password string
Hostname *string
Realm *string

tokenData *struct {
Token *gocloak.JWT
Expires time.Time
}
}

func (p *ProviderKeycloakUserPassword) refresh() error {
hostname := hostnameDefault
if p.Hostname != nil {
hostname = *p.Hostname
}
realm := realmDefault
if p.Realm != nil {
realm = *p.Realm
}
if p.tokenData == nil {
token, err := gocloak.NewClient(hostname).Login(context.Background(), p.ClientID, p.ClientSecret, realm, p.Username, p.Password)
if err != nil {
p.tokenData = nil
return err
}
p.tokenData = &struct {
Token *gocloak.JWT
Expires time.Time
}{Token: token, Expires: time.Now().Add(time.Duration(token.ExpiresIn-10) * time.Second)}
} else {
token, err := gocloak.NewClient(hostname).RefreshToken(context.Background(), p.tokenData.Token.RefreshToken, p.ClientID, p.ClientSecret, realm)
if err != nil {
p.tokenData = nil
return err
}
p.tokenData = &struct {
Token *gocloak.JWT
Expires time.Time
}{Token: token, Expires: time.Now().Add(time.Duration(token.ExpiresIn-10) * time.Second)}
}
return nil
}

func (p *ProviderKeycloakUserPassword) GetToken(ctx context.Context) (string, error) {
if p.tokenData == nil || p.tokenData.Expires.Before(time.Now()) {
if err := p.refresh(); err != nil {
return "", err
}
}
return p.tokenData.Token.AccessToken, nil
}
36 changes: 24 additions & 12 deletions pkg/gpcloud/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package client
import (
"crypto/tls"
"fmt"
"log"

"buf.build/gen/go/gportal/gportal-cloud/grpc/go/gpcloud/api/auth/v1/authv1grpc"
"buf.build/gen/go/gportal/gportal-cloud/grpc/go/gpcloud/api/cloud/v1/cloudv1grpc"
Expand All @@ -20,6 +21,8 @@ type Client struct {
grpcClient *grpc.ClientConn
}

type EndpointOverrideOption string

// CloudClient Returns the CloudServiceClient
func (c *Client) CloudClient() cloudv1grpc.CloudServiceClient {
return cloudv1grpc.NewCloudServiceClient(c.grpcClient)
Expand All @@ -46,29 +49,38 @@ func (c *Client) PaymentClient() paymentv1grpc.PaymentServiceClient {
}

// NewClient Returns a new GRPC client
func NewClient(authOptions AuthOptions, options ...grpc.DialOption) (*Client, error) {
func NewClient(extraOptions ...interface{}) (*Client, error) {
cl := &Client{}

var options []grpc.DialOption
// Certificate pinning
options = append(options, grpc.WithTransportCredentials(credentials.NewTLS(getTLSOptions())))

// User Agent
options = append(options, grpc.WithUserAgent(fmt.Sprintf("GPCloud Golang Client [%s]", Version)))

// Default auth loader
if authOptions.ClientID != "" &&
authOptions.ClientSecret != "" &&
authOptions.Username != "" &&
authOptions.Password != "" {
auth, err := NewAuth(&authOptions)
if err != nil {
return nil, err
endpoint := DefaultEndpoint
authenticationDefined := false
for _, option := range extraOptions {
if opt, ok := option.(grpc.DialOption); ok {
options = append(options, opt)
continue
}
if opt, ok := option.(EndpointOverrideOption); ok {
endpoint = string(opt)
continue
}
if opt, ok := option.(AuthProviderOption); ok && !authenticationDefined {
log.Printf("Using auth provider: %T", opt)
options = append(options, grpc.WithPerRPCCredentials(&AuthOption{
Provider: &opt,
}))
authenticationDefined = true
continue
}
// Access Token
options = append(options, grpc.WithPerRPCCredentials(auth))
}

clientConn, err := grpc.Dial(DefaultEndpoint, options...)
clientConn, err := grpc.Dial(endpoint, options...)
if err != nil {
return nil, err
}
Expand Down

0 comments on commit e8b8855

Please sign in to comment.