Skip to content

Commit

Permalink
Merge branch 'main' into feat/subject-mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
jakedoublev committed Mar 19, 2024
2 parents ae3384d + b001351 commit 494a8ab
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 6 deletions.
95 changes: 95 additions & 0 deletions cmd/auth-login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cmd

import (
"fmt"
"strings"

"github.com/opentdf/tructl/pkg/cli"
"github.com/opentdf/tructl/pkg/handlers"
"github.com/spf13/cobra"
"github.com/zalando/go-keyring"
)

var (
auth_loginCommands = []string{
// auth_loginPassword.Use,
auth_loginClientCredentials.Use,
}
)

var auth_loginCmd = &cobra.Command{
Use: "login",
Short: "Allows you to login in, in several ways [" + strings.Join(auth_loginCommands, ", ") + "]",
Long: `
Auth - Login - Allows you to login in via all of the supported OAuth2 methods`,
}

var auth_loginClientCredentials = &cobra.Command{
Use: "clientCredentials",
Short: "Allows the user to login in via clientId and clientSecret. This will subsequently be stored in the OS-specific keychain by default, but can be disabled with the --no-cache flag.",
Run: func(cmd *cobra.Command, args []string) {
h := cli.NewHandler(cmd)
defer h.Close()

flagHelper := cli.NewFlagHelper(cmd)
clientId := flagHelper.GetOptionalString("clientId")
clientSecret := flagHelper.GetOptionalString("clientSecret")
// noCache := flagHelper.GetOptionalString("noCache")
errMsg := fmt.Sprintf("Please provide required flag: (%s)", "Param Not Found")

// h.DEBUG_PrintKeyRingSecrets()

// check if we have a clientId in the keyring, if a null value is passed in
if clientId == "" {
fmt.Println("No clientId provided. Attempting to retrieve the default from keyring.")
retrievedClientID, errID := keyring.Get(handlers.TOKEN_URL, handlers.TRUCTL_CLIENT_ID_CACHE_KEY)
if errID == nil {
clientId = retrievedClientID
fmt.Println(cli.SuccessMessage("Retrieved stored clientId from keyring"))
}
}

// now lets check if we still don't have it, and if not, throw and error
if clientId == "" {
errMsg = fmt.Sprintf("Please provide required flag: (%s)", "clientId")
fmt.Println(cli.ErrorMessage(errMsg, nil))
cli.ExitWithError("Could not create attribute", nil)
return
}

// check if we have a clientSecret in the keyring, if a null value is passed in
if clientSecret == "" {
retrievedSecret, krErr := keyring.Get(handlers.TOKEN_URL, clientId)
if krErr == nil {
clientSecret = retrievedSecret
fmt.Println(cli.SuccessMessage("Retrieved stored clientSecret from keyring"))
}
}
// check if we still don't have it, and if not throw an error
if clientSecret == "" {
errMsg = fmt.Sprintf("Please provide required flag: (%s)", "clientSecret")
fmt.Println(cli.ErrorMessage(errMsg, nil))
cli.ExitWithError("Could not create attribute", nil)
return
}

// for now we're hardcoding the TOKEN_URL as a constant at the top
var _, err = h.GetTokenWithClientCredentials(clientId, clientSecret, handlers.TOKEN_URL, false)

if err != nil {
errMsg = cli.ErrorMessage("An error occurred during login. Please check your credentials and try again.", nil)
fmt.Println(errMsg)
cli.ExitWithError(errMsg, err)
return
}

fmt.Println(cli.SuccessMessage("Successfully logged in with clientId and clientSecret"))
},
}

func init() {
auth_loginCmd.AddCommand(auth_loginClientCredentials)
auth_loginClientCredentials.Flags().StringP("clientId", "i", "", "The client id")
auth_loginClientCredentials.Flags().StringP("clientSecret", "s", "", "The client secret")
authCmd.AddCommand(auth_loginCmd)
}
16 changes: 16 additions & 0 deletions cmd/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cmd

import "github.com/spf13/cobra"

var (
// authCmd is the command for managing local authentication session (login, logout, and token caching)
authCmd = &cobra.Command{
Use: "auth",
Short: "Manage local authentication session",
Long: `This command will allow you to manage your local authentication session in regards to the DSP platform.`,
}
)

func init() {
rootCmd.AddCommand(authCmd)
}
10 changes: 7 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,42 @@ module github.com/opentdf/tructl

go 1.22

toolchain go1.22.0
toolchain go1.22.1

require (
github.com/adrg/frontmatter v0.2.0
github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/huh v0.3.0
github.com/charmbracelet/lipgloss v0.10.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/muesli/reflow v0.3.0
github.com/opentdf/platform/protocol/go v0.0.0-20240313200110-bcc04e006182
github.com/opentdf/platform/sdk v0.0.0-20240313200110-bcc04e006182
github.com/spf13/cobra v1.8.0
github.com/zalando/go-keyring v0.2.3
golang.org/x/oauth2 v0.18.0
google.golang.org/grpc v1.62.1
)

require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240221180331-f05a6f4403ce.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/coreos/go-oidc/v3 v3.9.0 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
Expand Down Expand Up @@ -68,7 +73,6 @@ require (
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd4=
github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
Expand Down Expand Up @@ -41,6 +43,8 @@ github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -67,6 +71,8 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
Expand Down Expand Up @@ -179,6 +185,8 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand All @@ -195,6 +203,8 @@ github.com/virtru/access-pdp v1.11.0/go.mod h1:7OkDvrJX9qtzZ8KYFv7uvbp3IuhJZBqjV
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
Expand Down
152 changes: 152 additions & 0 deletions pkg/handlers/authLogin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package handlers

import (
"fmt"
"time"

"github.com/golang-jwt/jwt/v4"
"github.com/zalando/go-keyring"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)

const TRUCTL_CLIENT_ID_CACHE_KEY = "TRUCTL_DEFAULT_CLIENT_ID"
const TRUCTL_OIDC_TOKEN_KEY = "TRUCTL_OIDC_TOKEN"

// we're hardcoding this for now, but eventually it will be retrieved from the backend config
// TODO udpate to use the wellknown endpoint for the platform (https://github.com/opentdf/platform/pull/296)
const TOKEN_URL = "http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token"

// CheckTokenExpiration checks if an OIDC token has expired.
// Returns true if the token is still valid, false otherwise.
func CheckTokenExpiration(tokenString string) (bool, error) {
// for simplicity sake, we're skipping the token validation, and just checking the expiration time, if expired we'll get a new token
token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
if err != nil {
return false, err // Token could not be parsed
}

if claims, ok := token.Claims.(jwt.MapClaims); ok {
if exp, ok := claims["exp"].(float64); ok {
expirationTime := time.Unix(int64(exp), 0)
return time.Now().Before(expirationTime), nil // Return true if the current time is before the expiration time
}
}

// Return an error if the expiration time could not be found or parsed
return false, fmt.Errorf("expiration time (exp) claim is missing or invalid")
}

// GetOIDCTokenFromCache retrieves the OIDC token from the keyring.
func GetOIDCTokenFromCache() (string, error) {
token, err := keyring.Get(TOKEN_URL, TRUCTL_OIDC_TOKEN_KEY)
if err != nil {
return "", err
}
return token, nil
}

// GetClientIDFromCache retrieves the client ID from the keyring.
func GetClientIDFromCache() (string, error) {
clientId, err := keyring.Get(TOKEN_URL, TRUCTL_CLIENT_ID_CACHE_KEY)
if err != nil {
return "", err
}
return clientId, nil
}

// GetClientSecretFromCache retrieves the client secret from the keyring.
func GetClientIdAndSecretFromCache() (string, string, error) {
// our clientSecret key, is our clientId, so we gotta grab that first
clientId, err := keyring.Get(TOKEN_URL, TRUCTL_CLIENT_ID_CACHE_KEY)
if err != nil {
// we failed to get the clientId for somereason
return "", "", err
}

if clientId == "" {
return "", "", fmt.Errorf("no clientId found in keyring")
}

clientSecret, err := keyring.Get(TOKEN_URL, clientId)
if err != nil {
return "", "", err
}
return clientSecret, clientId, nil
}

// DEBUG_PrintKeyRingSecrets prints all the secrets in the keyring.
func (h *Handler) DEBUG_PrintKeyRingSecrets() {

clientId, err := keyring.Get(TOKEN_URL, TRUCTL_CLIENT_ID_CACHE_KEY)
if err != nil {
fmt.Println("Failed to retrieve secret from keyring:", err)
return
}

// and our special clientId key, to grab the secret
secret, errSec := keyring.Get(TOKEN_URL, clientId)
OIDC_TOKEN, errToken := keyring.Get(TOKEN_URL, TRUCTL_OIDC_TOKEN_KEY)

if errSec != nil {
fmt.Println("Failed to retrieve secret from keyring:", err)
return
}

if errToken != nil {
fmt.Println("Failed to retrieve secret from keyring:", errToken)
return
}

fmt.Println(clientId, ":", secret)
fmt.Println("Stored OIDC_TOKEN OF:", OIDC_TOKEN)
}

// GetTokenWithClientCredentials uses the OAuth2 client credentials flow to obtain a token.
func (h *Handler) GetTokenWithClientCredentials(clientID, clientSecret, tokenURL string, noCache bool) (*oauth2.Token, error) {
// did the user pass a custom tokenURL?
if tokenURL == "" {
// use the default hardcoded constant
tokenURL = TOKEN_URL
}

config := clientcredentials.Config{
ClientID: clientID,
ClientSecret: clientSecret,
TokenURL: tokenURL,
}

token, err := config.Token(h.ctx)
if err != nil {
return nil, err
}

// if the users didn't specifically specify not to cache, then we'll cache the clientID, clientSecret, and OIDC_TOKEN in the keyring
if !noCache {
// lets store our id and secret in the keyring
errID := keyring.Set(tokenURL, TRUCTL_CLIENT_ID_CACHE_KEY, clientID)
err := keyring.Set(tokenURL, clientID, clientSecret)
// lets also store the oidc token
errToken := keyring.Set(tokenURL, TRUCTL_OIDC_TOKEN_KEY, token.AccessToken)
if err != nil {
return nil, err
}

if errID != nil {
return nil, fmt.Errorf("failed to store client ID in keyring: %v", errID)
}

if errToken != nil {
return nil, fmt.Errorf("failed to store OIDC Token in keyring: %v", errToken)
}
}
h.OIDC_TOKEN = token.AccessToken
return token, nil
}

// GetTokenWithPasswordFlow creates a custom request to obtain a token using the resource owner password credentials flow.
func (h *Handler) GetTokenWithPasswordFlow(username, password, clientID, clientSecret, tokenURL string, noCache bool) (string, error) {
errMsg := "Method `GetTokenWithPasswordFlow` is not yet implemented. Please reach out to a Virtru Platform team member to inquire about the status of it."
fmt.Println(errMsg)
return "", nil
}
21 changes: 18 additions & 3 deletions pkg/handlers/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,30 @@ import (
var SDK *sdk.SDK

type Handler struct {
sdk *sdk.SDK
ctx context.Context
sdk *sdk.SDK
ctx context.Context
OIDC_TOKEN string
}

func New(platformEndpoint string) (Handler, error) {
// define the scopes in an array
// scopes := []string{"email"}
// normally, we should try to retrieve an active OICD token here, however, the SDK has no option for passing a token
// so instead, we'll check if we have a clientId and clientSecret stored, and if so, we'll use those to init the SDK, otherwise, we'll use the insecure connection (which will stop working once we enforce auth on the backend)
// clientSecret, clientId, err := GetClientIdAndSecretFromCache()

// sdk, err := sdk.New(platformEndpoint, sdk.WithClientCredentials("client-id", "clientSecret", scopes), sdk.WithTokenEndpoint("http://dummy/token-endpoint"))
// if err != nil {
// return Handler{}, err
// }

// scopes := []string{"email"}
// create the sdk with the client credentials
//NOTE FROM AVERY: The below line is commented out because although it should work, the SDK
// is having trouble with the "WithClientCredentials" endpoint
// so although the commented out line should work, and will work in the future, today it doesn't, so
// to facilitate development, we're leaving it commented, until the SDK is fixed, and using the insecure connection instead
// note that for now we're hard coding the TOKEN_URL until we have an endpoint to get the config from
// sdk, err := sdk.New(platformEndpoint, sdk.WithClientCredentials(clientId, clientSecret, scopes), sdk.WithTokenEndpoint(TOKEN_URL))
sdk, err := sdk.New(platformEndpoint, sdk.WithInsecureConn())
if err != nil {
return Handler{}, err
Expand Down

0 comments on commit 494a8ab

Please sign in to comment.