41
41
AlwaysPrompt : false ,
42
42
}
43
43
44
+ loginClientAssertionSigningKey = Flag {
45
+ Name : "Client Assertion Signing Key" ,
46
+ LongForm : "client-assertion-signing-key" ,
47
+ Help : "Client Assertion ." ,
48
+ IsRequired : false ,
49
+ AlwaysPrompt : false ,
50
+ }
51
+
52
+ loginClientAssertionSigningAlg = Flag {
53
+ Name : "Client Assertion Signing Algorithm" ,
54
+ LongForm : "client-assertion-signing-alg" ,
55
+ Help : "Client Assertion Signing Algorithm." ,
56
+ IsRequired : false ,
57
+ AlwaysPrompt : false ,
58
+ }
59
+
44
60
loginAdditionalScopes = Flag {
45
61
Name : "Additional Scopes" ,
46
62
LongForm : "scopes" ,
@@ -51,10 +67,12 @@ var (
51
67
)
52
68
53
69
type LoginInputs struct {
54
- Domain string
55
- ClientID string
56
- ClientSecret string
57
- AdditionalScopes []string
70
+ Domain string
71
+ ClientID string
72
+ ClientSecret string
73
+ ClientAssertionSigningKey string
74
+ ClientAssertionSigningAlg string
75
+ AdditionalScopes []string
58
76
}
59
77
60
78
func (i * LoginInputs ) isLoggingInWithAdditionalScopes () bool {
@@ -78,7 +96,7 @@ func loginCmd(cli *cli) *cobra.Command {
78
96
RunE : func (cmd * cobra.Command , args []string ) error {
79
97
var selectedLoginType string
80
98
const loginAsUser , loginAsMachine = "As a user" , "As a machine"
81
- shouldLoginAsUser , shouldLoginAsMachine := false , false
99
+ shouldLoginAsUser , shouldLoginAsMachine , shouldLoginAsMachineWithJWT := false , false , false
82
100
83
101
/*
84
102
Based on the initial inputs we'd like to determine if
@@ -95,35 +113,59 @@ func loginCmd(cli *cli) *cobra.Command {
95
113
case inputs .Domain != "" && inputs .ClientSecret != "" && inputs .ClientID != "" :
96
114
// If all three fields are passed, machine login flag is set to true.
97
115
shouldLoginAsMachine = true
98
- case inputs .Domain != "" && inputs .ClientSecret == "" && inputs .ClientID == "" :
116
+ case inputs .Domain != "" && inputs .ClientID != "" && inputs .ClientAssertionSigningAlg != "" && inputs .ClientAssertionSigningKey != "" :
117
+ // If all four fields are passed related to client Assertion, machine login with jwt flag is set to true.
118
+ shouldLoginAsMachineWithJWT = true
119
+ case inputs .Domain != "" && inputs .ClientSecret == "" && inputs .ClientID == "" && inputs .ClientAssertionSigningAlg == "" && inputs .ClientAssertionSigningKey == "" :
99
120
/*
100
121
The domain flag is common between Machine and User Login.
101
122
If domain is passed without client-id and client-secret,
102
123
it can be evaluated that it is a user login flow.
103
124
*/
104
125
shouldLoginAsUser = true
105
- case inputs .Domain != "" || inputs .ClientSecret != "" || inputs .ClientID != "" :
126
+ case inputs .Domain != "" || inputs .ClientSecret != "" || inputs .ClientID != "" || inputs . ClientAssertionSigningAlg != "" || inputs . ClientAssertionSigningKey != "" :
106
127
/*
107
128
At this point, if AT LEAST one of the three flags are passed but not ALL three,
108
129
we return an error since it's a no-input flow and it will need all three params
109
130
for successful machine flow.
110
131
Note that we already determined it's not a user login flow in the condition above.
111
132
*/
112
- return fmt .Errorf ("flags client-id, client-secret and domain are required together" )
133
+ return fmt .Errorf ("flags client-id, client-secret and domain are required together or client-id, client-assertion-signing-alg, client-assertion-signing-key and domain are required together " )
113
134
default :
114
135
/*
115
136
If no flags are passed along with --no-input, it is defaulted to user login flow.
116
137
*/
117
138
shouldLoginAsUser = true
118
139
}
119
140
default :
120
- if inputs .ClientSecret != "" || inputs .ClientID != "" {
141
+ if inputs .ClientID != "" {
142
+ const clientSecret , clientAssertion = "Client Secret" , "Client Assertion"
143
+ input := prompt .SelectInput ("" , "How would you like to authenticate?" , "" , []string {clientSecret , clientAssertion }, clientSecret , true )
144
+ if err := prompt .AskOne (input , & selectedLoginType ); err != nil {
145
+ return handleInputError (err )
146
+ }
147
+ if selectedLoginType == clientAssertion {
148
+ shouldLoginAsMachineWithJWT = true
149
+ } else {
150
+ shouldLoginAsMachine = true
151
+ }
152
+ }
153
+
154
+ if inputs .ClientSecret != "" {
121
155
/*
122
156
If all three params are passed, we evaluate it as a Machine Login Flow.
123
157
Else required params are prompted for.
124
158
*/
125
159
shouldLoginAsMachine = true
126
160
}
161
+
162
+ if inputs .ClientAssertionSigningAlg != "" || inputs .ClientAssertionSigningKey != "" {
163
+ /*
164
+ If all four params are passed, we evaluate it as a Machine Login Flow.
165
+ Else required params are prompted for.
166
+ */
167
+ shouldLoginAsMachineWithJWT = true
168
+ }
127
169
}
128
170
129
171
// If additional scopes are passed we mark shouldLoginAsUser flag to be true.
@@ -136,7 +178,7 @@ func loginCmd(cli *cli) *cobra.Command {
136
178
based on all the evaluation above, we go on to prompt the user and
137
179
determine if it's LoginAsUser or LoginAsMachine
138
180
*/
139
- if ! shouldLoginAsUser && ! shouldLoginAsMachine {
181
+ if ! shouldLoginAsUser && ! shouldLoginAsMachine && ! shouldLoginAsMachineWithJWT {
140
182
cli .renderer .Output (
141
183
fmt .Sprintf (
142
184
"%s\n \n %s\n %s\n \n %s\n %s\n %s\n %s\n \n " ,
@@ -317,6 +359,7 @@ func RunLoginAsUser(ctx context.Context, cli *cli, additionalScopes []string, do
317
359
318
360
// RunLoginAsMachine facilitates the authentication process using client credentials (client ID, client secret).
319
361
func RunLoginAsMachine (ctx context.Context , inputs LoginInputs , cli * cli , cmd * cobra.Command ) error {
362
+ // Yet to handle the case with clientJWT Assertions
320
363
if err := loginTenantDomain .Ask (cmd , & inputs .Domain , nil ); err != nil {
321
364
return err
322
365
}
@@ -371,3 +414,70 @@ func RunLoginAsMachine(ctx context.Context, inputs LoginInputs, cli *cli, cmd *c
371
414
372
415
return nil
373
416
}
417
+
418
+ // RunLoginAsMachineJWT facilitates the authentication process using the client credentials
419
+ // with Private Key JWT authentication flow. (client ID, client Assertion Signing key).
420
+ func RunLoginAsMachineJWT (ctx context.Context , inputs LoginInputs , cli * cli , cmd * cobra.Command ) error {
421
+ if err := loginTenantDomain .Ask (cmd , & inputs .Domain , nil ); err != nil {
422
+ return err
423
+ }
424
+
425
+ if err := loginClientID .Ask (cmd , & inputs .ClientID , nil ); err != nil {
426
+ return err
427
+ }
428
+
429
+ if err := loginClientAssertionSigningAlg .Ask (cmd , & inputs .ClientAssertionSigningAlg , nil ); err != nil {
430
+ return err
431
+ }
432
+
433
+ if err := loginClientAssertionSigningKey .AskPassword (cmd , & inputs .ClientAssertionSigningKey ); err != nil {
434
+ return err
435
+ }
436
+
437
+ domain := "https://" + inputs .Domain
438
+
439
+ token , err := auth .GetAccessTokenFromClientPrivateJWT (
440
+ auth.PrivateKeyJwtTokenSource {
441
+ Ctx : ctx ,
442
+ ClientID : inputs .ClientID ,
443
+ ClientAssertionSigningAlg : inputs .ClientAssertionSigningAlg ,
444
+ Uri : domain ,
445
+ Audience : domain + "/api/v2/" ,
446
+ ClientAssertionSigningKey : inputs .ClientAssertionSigningKey ,
447
+ },
448
+ )
449
+
450
+ if err != nil {
451
+ return fmt .Errorf ("failed to fetch access token using client credentials with Private Key. \n \n Ensure that the provided client-id, client-assertion-signing-key, client-assertion-signing-alg and domain are correct. \n \n error: %w" , err )
452
+ }
453
+
454
+ tenant := config.Tenant {
455
+ Name : strings .Split (inputs .Domain , "." )[0 ],
456
+ Domain : inputs .Domain ,
457
+ ExpiresAt : token .ExpiresAt ,
458
+ ClientID : inputs .ClientID ,
459
+ }
460
+
461
+ if err = keyring .StoreClientSecret (inputs .Domain , inputs .ClientSecret ); err != nil {
462
+ cli .renderer .Warnf ("Could not store the client secret and the access token to the keyring: %s" , err )
463
+ cli .renderer .Warnf ("Expect to login again when your access token expires." )
464
+ }
465
+
466
+ if err := keyring .StoreAccessToken (inputs .Domain , token .AccessToken ); err != nil {
467
+ // In case we don't have a keyring, we want the
468
+ // access token to be saved in the config file.
469
+ tenant .AccessToken = token .AccessToken
470
+ }
471
+
472
+ if err = cli .Config .AddTenant (tenant ); err != nil {
473
+ return fmt .Errorf ("failed to save tenant data: %w" , err )
474
+ }
475
+
476
+ cli .renderer .Newline ()
477
+ cli .renderer .Infof ("Successfully logged in." )
478
+ cli .renderer .Infof ("Tenant: %s" , inputs .Domain )
479
+
480
+ cli .tracker .TrackFirstLogin (cli .Config .InstallID )
481
+
482
+ return nil
483
+ }
0 commit comments