• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 14906409187

08 May 2025 12:25PM UTC coverage: 90.605% (+0.07%) from 90.539%
14906409187

push

github

thschmitt
Add support for user login flow using confidential applications

- Added support for user login flows uing the browser with
  confidential apps.

- Parsing the clientSecret from the auth config and passing it in
  the `POST /connect/token` request

- Extended the `uipath config --auth login` command to, optionally,
  ask for client secret

- Created ConfigBuilder struct to better keep track of the inputted
  values of the user so that it is clear whether the user skipped over a
  value or entered a whitespace, for example.

- Added environment variable support for all basic auth values:
  `UIPATH_AUTH_GRANT_TYPE`: overrides the identity token request grant type (default client_credentials)
  `UIPATH_AUTH_SCOPES`: overrides the scopes
  `UIPATH_AUTH_REDIRECT_URI`: overrides the redirect uri for the OAuth flow

Example command:

```
uipath config --auth login

Enter organization [not set]: my-org
Enter tenant [not set]: my-tenant
Enter client id [not set]: 735db4d1-89d3-49c8-ad80-01c7ff871655
Enter client secret (only for confidential apps) [not set]: my-secret
Enter redirect uri [not set]: http://localhost:12700
Enter scopes [not set]: OR.Users
Successfully configured uipath CLI
```

Example config:

```
- name: default
  organization: my-org
  tenant: my-tenant
  auth:
    clientId: 735db4d1-89d3-49c8-ad80-01c7ff871655
    clientSecret: my-secret
    redirectUri: http://localhost:12700
    scopes: OR.Users
```

Implements https://github.com/UiPath/uipathcli/issues/192

234 of 254 new or added lines in 8 files covered. (92.13%)

3 existing lines in 3 files now uncovered.

6500 of 7174 relevant lines covered (90.6%)

1.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.67
/commandline/config_command_handler.go
1
package commandline
2

3
import (
4
        "bufio"
5
        "fmt"
6
        "io"
7
        "strings"
8

9
        "github.com/UiPath/uipathcli/config"
10
)
11

12
// configCommandHandler implements commands for configuring the CLI.
13
// The CLI can be configured interactively or by setting config values
14
// programmatically.
15
//
16
// Example:
17
// uipath config ==> interactive configuration of the CLI
18
// uipath config set ==> stores a value in the configuration file
19
type configCommandHandler struct {
20
        StdIn          io.Reader
21
        StdOut         io.Writer
22
        ConfigProvider config.ConfigProvider
23
}
24

25
const notSetMessage = "not set"
26
const maskMessage = "*******"
27
const successfullyConfiguredMessage = "Successfully configured uipath CLI"
28
const successfullySetMessage = "Successfully set config value"
29

30
const CredentialsAuth = "credentials"
31
const LoginAuth = "login"
32
const PatAuth = "pat"
33

34
func (h configCommandHandler) Set(key string, value string, profileName string) error {
1✔
35
        cfg := h.getOrCreateProfile(profileName)
1✔
36
        err := h.setConfigValue(&cfg, key, value)
1✔
37
        if err != nil {
2✔
38
                return err
1✔
39
        }
1✔
40
        err = h.ConfigProvider.Update(profileName, cfg)
1✔
41
        if err != nil {
1✔
42
                return err
×
43
        }
×
44
        _, _ = fmt.Fprintln(h.StdOut, successfullySetMessage)
1✔
45
        return nil
1✔
46
}
47

48
func (h configCommandHandler) setConfigValue(cfg *config.Config, key string, value string) error {
1✔
49
        keyParts := strings.Split(key, ".")
1✔
50
        if key == "serviceVersion" {
2✔
51
                cfg.SetServiceVersion(value)
1✔
52
                return nil
1✔
53
        } else if key == "organization" {
3✔
54
                cfg.SetOrganization(value)
1✔
55
                return nil
1✔
56
        } else if key == "tenant" {
3✔
57
                cfg.SetTenant(value)
1✔
58
                return nil
1✔
59
        } else if key == "uri" {
3✔
60
                return cfg.SetUri(value)
1✔
61
        } else if key == "insecure" {
3✔
62
                insecure, err := h.convertToBool(value)
1✔
63
                if err != nil {
2✔
64
                        return fmt.Errorf("Invalid value for 'insecure': %w", err)
1✔
65
                }
1✔
66
                cfg.SetInsecure(insecure)
1✔
67
                return nil
1✔
68
        } else if key == "debug" {
2✔
69
                debug, err := h.convertToBool(value)
1✔
70
                if err != nil {
2✔
71
                        return fmt.Errorf("Invalid value for 'debug': %w", err)
1✔
72
                }
1✔
73
                cfg.SetDebug(debug)
1✔
74
                return nil
1✔
75
        } else if key == "auth.grantType" {
2✔
76
                cfg.SetAuthGrantType(value)
1✔
77
                return nil
1✔
78
        } else if key == "auth.scopes" {
3✔
79
                cfg.SetAuthScopes(value)
1✔
80
                return nil
1✔
81
        } else if h.isHeaderKey(keyParts) {
3✔
82
                cfg.SetHeader(keyParts[1], value)
1✔
83
                return nil
1✔
84
        } else if h.isParameterKey(keyParts) {
3✔
85
                cfg.SetParameter(keyParts[1], value)
1✔
86
                return nil
1✔
87
        } else if h.isAuthPropertyKey(keyParts) {
3✔
88
                cfg.SetAuthProperty(keyParts[2], value)
1✔
89
                return nil
1✔
90
        }
1✔
91
        return fmt.Errorf("Unknown config key '%s'", key)
1✔
92
}
93

94
func (h configCommandHandler) isHeaderKey(keyParts []string) bool {
1✔
95
        return len(keyParts) == 2 && keyParts[0] == "header"
1✔
96
}
1✔
97

98
func (h configCommandHandler) isParameterKey(keyParts []string) bool {
1✔
99
        return len(keyParts) == 2 && keyParts[0] == "parameter"
1✔
100
}
1✔
101

102
func (h configCommandHandler) isAuthPropertyKey(keyParts []string) bool {
1✔
103
        return len(keyParts) == 3 && keyParts[0] == "auth" && keyParts[1] == "properties"
1✔
104
}
1✔
105

106
func (h configCommandHandler) convertToBool(value string) (bool, error) {
1✔
107
        if strings.EqualFold(value, "true") {
2✔
108
                return true, nil
1✔
109
        }
1✔
110
        if strings.EqualFold(value, "false") {
1✔
111
                return false, nil
×
112
        }
×
113
        return false, fmt.Errorf("Invalid boolean value: %s", value)
1✔
114
}
115

116
func (h configCommandHandler) Configure(auth string, profileName string) error {
1✔
117
        switch auth {
1✔
118
        case CredentialsAuth:
1✔
119
                return h.configureCredentials(profileName)
1✔
120
        case LoginAuth:
1✔
121
                return h.configureLogin(profileName)
1✔
122
        case PatAuth:
1✔
123
                return h.configurePat(profileName)
1✔
124
        case "":
1✔
125
                return h.configure(profileName)
1✔
126
        }
127
        return fmt.Errorf("Invalid auth, supported values: %s, %s, %s", CredentialsAuth, LoginAuth, PatAuth)
×
128
}
129

130
func (h configCommandHandler) configure(profileName string) error {
1✔
131
        cfg := h.getOrCreateProfile(profileName)
1✔
132
        reader := bufio.NewReader(h.StdIn)
1✔
133

1✔
134
        builder := config.NewConfigBuilder(cfg)
1✔
135
        err := h.readOrgTenantInput(builder, reader)
1✔
136
        if err != nil {
1✔
137
                return nil
×
138
        }
×
139
        err = h.readAuthInput(builder, reader)
1✔
140
        if err != nil {
2✔
141
                return nil
1✔
142
        }
1✔
143

144
        return h.updateConfigIfNeeded(builder, profileName)
1✔
145
}
146

147
func (h configCommandHandler) readAuthInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
148
        authType := h.readAuthTypeInput(builder.Config, reader)
1✔
149
        switch authType {
1✔
150
        case CredentialsAuth:
1✔
151
                return h.readCredentialsInput(builder, reader)
1✔
152
        case LoginAuth:
1✔
153
                return h.readLoginInput(builder, reader)
1✔
154
        case PatAuth:
1✔
155
                return h.readPatInput(builder, reader)
1✔
156
        default:
1✔
157
                return nil
1✔
158
        }
159
}
160

161
func (h configCommandHandler) configureCredentials(profileName string) error {
1✔
162
        cfg := h.getOrCreateProfile(profileName)
1✔
163
        reader := bufio.NewReader(h.StdIn)
1✔
164

1✔
165
        builder := config.NewConfigBuilder(cfg)
1✔
166
        err := h.readOrgTenantInput(builder, reader)
1✔
167
        if err != nil {
1✔
168
                return nil
×
169
        }
×
170
        err = h.readCredentialsInput(builder, reader)
1✔
171
        if err != nil {
1✔
172
                return nil
×
173
        }
×
174

175
        return h.updateConfigIfNeeded(builder, profileName)
1✔
176
}
177

178
func (h configCommandHandler) configureLogin(profileName string) error {
1✔
179
        cfg := h.getOrCreateProfile(profileName)
1✔
180
        reader := bufio.NewReader(h.StdIn)
1✔
181

1✔
182
        builder := config.NewConfigBuilder(cfg)
1✔
183
        err := h.readOrgTenantInput(builder, reader)
1✔
184
        if err != nil {
1✔
185
                return nil
×
186
        }
×
187
        err = h.readLoginInput(builder, reader)
1✔
188
        if err != nil {
2✔
189
                return nil
1✔
190
        }
1✔
191

192
        return h.updateConfigIfNeeded(builder, profileName)
1✔
193
}
194

195
func (h configCommandHandler) configurePat(profileName string) error {
1✔
196
        cfg := h.getOrCreateProfile(profileName)
1✔
197
        reader := bufio.NewReader(h.StdIn)
1✔
198

1✔
199
        builder := config.NewConfigBuilder(cfg)
1✔
200
        err := h.readOrgTenantInput(builder, reader)
1✔
201
        if err != nil {
1✔
202
                return nil
×
203
        }
×
204
        err = h.readPatInput(builder, reader)
1✔
205
        if err != nil {
1✔
206
                return nil
×
207
        }
×
208

209
        return h.updateConfigIfNeeded(builder, profileName)
1✔
210
}
211

212
func (h configCommandHandler) updateConfigIfNeeded(builder *config.ConfigBuilder, profileName string) error {
1✔
213
        updatedConfig, changed := builder.Build()
1✔
214
        if !changed {
2✔
215
                return nil
1✔
216
        }
1✔
217

218
        err := h.ConfigProvider.Update(profileName, updatedConfig)
1✔
219
        if err != nil {
1✔
NEW
220
                return err
×
NEW
221
        }
×
222
        _, _ = fmt.Fprintln(h.StdOut, successfullyConfiguredMessage)
1✔
223
        return nil
1✔
224
}
225

226
func (h configCommandHandler) getAuthType(cfg config.Config) string {
1✔
227
        if cfg.Pat() != "" {
2✔
228
                return PatAuth
1✔
229
        }
1✔
230
        if cfg.ClientId() != "" && cfg.RedirectUri() != "" && cfg.Scopes() != "" {
2✔
231
                return LoginAuth
1✔
232
        }
1✔
233
        if cfg.ClientId() != "" && cfg.ClientSecret() != "" {
2✔
234
                return CredentialsAuth
1✔
235
        }
1✔
236
        return ""
1✔
237
}
238

239
func (h configCommandHandler) getOrCreateProfile(profileName string) config.Config {
1✔
240
        cfg := h.ConfigProvider.Config(profileName)
1✔
241
        if cfg == nil {
2✔
242
                return h.ConfigProvider.New()
1✔
243
        }
1✔
244
        return *cfg
1✔
245
}
246

247
func (h configCommandHandler) getDisplayValue(value string, masked bool) string {
1✔
248
        if value == "" {
2✔
249
                return notSetMessage
1✔
250
        }
1✔
251
        if masked {
2✔
252
                return h.maskValue(value)
1✔
253
        }
1✔
254
        return value
1✔
255
}
256

257
func (h configCommandHandler) maskValue(value string) string {
1✔
258
        if len(value) < 10 {
2✔
259
                return maskMessage
1✔
260
        }
1✔
261
        return maskMessage + value[len(value)-4:]
1✔
262
}
263

264
func (h configCommandHandler) readUserInput(message string, reader *bufio.Reader) (*string, error) {
1✔
265
        _, err := fmt.Fprint(h.StdOut, message+" ")
1✔
266
        if err != nil {
1✔
NEW
267
                return nil, err
×
268
        }
×
269
        value, err := reader.ReadString('\n')
1✔
270
        if err != nil {
2✔
271
                return nil, err
1✔
272
        }
1✔
273
        value = strings.Trim(value, "\r\n")
1✔
274
        if value == "" {
2✔
275
                return nil, nil
1✔
276
        }
1✔
277
        value = strings.Trim(value, " \t")
1✔
278
        return &value, nil
1✔
279
}
280

281
func (h configCommandHandler) readOrgTenantInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
282
        cfg := builder.Config
1✔
283
        message := fmt.Sprintf("Enter organization [%s]:", h.getDisplayValue(cfg.Organization, false))
1✔
284
        organization, err := h.readUserInput(message, reader)
1✔
285
        if err != nil {
1✔
NEW
286
                return err
×
287
        }
×
288

289
        message = fmt.Sprintf("Enter tenant [%s]:", h.getDisplayValue(cfg.Tenant, false))
1✔
290
        tenant, err := h.readUserInput(message, reader)
1✔
291
        if err != nil {
1✔
NEW
292
                return err
×
293
        }
×
294

295
        builder.
1✔
296
                WithOrganization(organization).
1✔
297
                WithTenant(tenant)
1✔
298
        return nil
1✔
299
}
300

301
func (h configCommandHandler) readCredentialsInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
302
        cfg := builder.Config
1✔
303
        message := fmt.Sprintf("Enter client id [%s]:", h.getDisplayValue(cfg.ClientId(), true))
1✔
304
        clientId, err := h.readUserInput(message, reader)
1✔
305
        if err != nil {
1✔
NEW
306
                return err
×
307
        }
×
308

309
        message = fmt.Sprintf("Enter client secret [%s]:", h.getDisplayValue(cfg.ClientSecret(), true))
1✔
310
        clientSecret, err := h.readUserInput(message, reader)
1✔
311
        if err != nil {
2✔
312
                return err
1✔
313
        }
1✔
314

315
        builder.WithCredentials(clientId, clientSecret)
1✔
316
        return nil
1✔
317
}
318

319
func (h configCommandHandler) readLoginInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
320
        cfg := builder.Config
1✔
321
        message := fmt.Sprintf("Enter client id [%s]:", h.getDisplayValue(cfg.ClientId(), true))
1✔
322
        clientId, err := h.readUserInput(message, reader)
1✔
323
        if err != nil {
1✔
NEW
324
                return err
×
NEW
325
        }
×
326
        message = fmt.Sprintf("Enter client secret (only for confidential apps) [%s]:", h.getDisplayValue(cfg.ClientSecret(), true))
1✔
327
        clientSecret, err := h.readUserInput(message, reader)
1✔
328
        if err != nil {
1✔
NEW
329
                return err
×
UNCOV
330
        }
×
331
        message = fmt.Sprintf("Enter redirect uri [%s]:", h.getDisplayValue(cfg.RedirectUri(), false))
1✔
332
        redirectUri, err := h.readUserInput(message, reader)
1✔
333
        if err != nil {
2✔
334
                return err
1✔
335
        }
1✔
336
        message = fmt.Sprintf("Enter scopes [%s]:", h.getDisplayValue(cfg.Scopes(), false))
1✔
337
        scopes, err := h.readUserInput(message, reader)
1✔
338
        if err != nil {
2✔
339
                return err
1✔
340
        }
1✔
341

342
        builder.WithLogin(clientId, clientSecret, redirectUri, scopes)
1✔
343
        return nil
1✔
344
}
345

346
func (h configCommandHandler) readPatInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
347
        cfg := builder.Config
1✔
348
        message := fmt.Sprintf("Enter personal access token [%s]:", h.getDisplayValue(cfg.Pat(), true))
1✔
349
        pat, err := h.readUserInput(message, reader)
1✔
350
        if err != nil {
2✔
351
                return err
1✔
352
        }
1✔
353

354
        builder.WithPat(pat)
1✔
355
        return nil
1✔
356
}
357

358
func (h configCommandHandler) readAuthTypeInput(cfg config.Config, reader *bufio.Reader) string {
1✔
359
        authType := h.getAuthType(cfg)
1✔
360
        for {
2✔
361
                message := fmt.Sprintf(`Authentication type [%s]:
1✔
362
  [1] credentials - Client Id and Client Secret
1✔
363
  [2] login - OAuth login using the browser
1✔
364
  [3] pat - Personal Access Token
1✔
365
Select:`, h.getDisplayValue(authType, false))
1✔
366
                input, err := h.readUserInput(message, reader)
1✔
367
                if err != nil {
1✔
368
                        return ""
×
369
                }
×
370
                if input == nil {
2✔
371
                        return authType
1✔
372
                }
1✔
373
                switch *input {
1✔
NEW
374
                case "":
×
375
                case "1":
1✔
376
                        return CredentialsAuth
1✔
377
                case "2":
1✔
378
                        return LoginAuth
1✔
379
                case "3":
1✔
380
                        return PatAuth
1✔
381
                }
382
        }
383
}
384

385
func newConfigCommandHandler(stdIn io.Reader, stdOut io.Writer, configProvider config.ConfigProvider) *configCommandHandler {
1✔
386
        return &configCommandHandler{
1✔
387
                StdIn:          stdIn,
1✔
388
                StdOut:         stdOut,
1✔
389
                ConfigProvider: configProvider,
1✔
390
        }
1✔
391
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc