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

UiPath / uipathcli / 18678624158

21 Oct 2025 09:01AM UTC coverage: 90.797% (-0.08%) from 90.88%
18678624158

push

github

thschmitt
Improve configure command by providing tenant list

499 of 548 new or added lines in 18 files covered. (91.06%)

17 existing lines in 4 files now uncovered.

7044 of 7758 relevant lines covered (90.8%)

1.02 hits per line

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

87.41
/commandline/config_command_handler.go
1
package commandline
2

3
import (
4
        "bufio"
5
        "fmt"
6
        "io"
7
        "sort"
8
        "strconv"
9
        "strings"
10
        "time"
11

12
        "github.com/UiPath/uipathcli/auth"
13
        "github.com/UiPath/uipathcli/config"
14
        "github.com/UiPath/uipathcli/log"
15
        "github.com/UiPath/uipathcli/utils/api"
16
        "github.com/UiPath/uipathcli/utils/network"
17
        "github.com/UiPath/uipathcli/utils/stream"
18
        "github.com/UiPath/uipathcli/utils/visualization"
19
)
20

21
// configCommandHandler implements command for configuring the CLI interactively.
22
type configCommandHandler struct {
23
        Input          stream.Stream
24
        StdOut         io.Writer
25
        Logger         log.Logger
26
        ConfigProvider config.ConfigProvider
27
        Authenticators []auth.Authenticator
28
}
29

30
const notSetMessage = "not set"
31
const maskMessage = "*******"
32
const successfullyConfiguredMessage = "Successfully configured uipath CLI"
33

34
const getTenantsTimeout = time.Duration(60) * time.Second
35
const getTenantsMaxAttempts = 3
36

37
func (h configCommandHandler) Configure(input configCommandInput) error {
1✔
38
        stdin, err := h.Input.Data()
1✔
39
        if err != nil {
1✔
UNCOV
40
                return err
×
UNCOV
41
        }
×
42
        defer func() { _ = stdin.Close() }()
2✔
43

44
        cfg := h.getOrCreateProfile(input.Profile)
1✔
45
        reader := bufio.NewReader(stdin)
1✔
46

1✔
47
        builder := config.NewConfigBuilder(cfg)
1✔
48
        authType := input.AuthType
1✔
49
        if authType == "" {
2✔
50
                authType = h.readAuthTypeInput(builder, reader)
1✔
51
        }
1✔
52
        err = h.readAuthInput(builder, reader, authType)
1✔
53
        if err != nil {
1✔
NEW
54
                return nil
×
55
        }
×
56

57
        organization, err := h.getOrganization(builder, input)
1✔
58
        if err != nil {
2✔
59
                return h.handleAuthError(err, builder, reader, input.Profile, authType)
1✔
60
        }
1✔
61
        if organization == nil {
2✔
62
                err := h.readOrgTenantInput(builder, reader)
1✔
63
                if err != nil {
1✔
NEW
64
                        return nil
×
UNCOV
65
                }
×
66
                return h.updateConfigIfNeeded(builder, input.Profile)
1✔
67
        }
68
        err = h.readTenantInput(builder, reader, *organization)
1✔
69
        if err != nil {
1✔
UNCOV
70
                return nil
×
UNCOV
71
        }
×
72
        return h.updateConfigIfNeeded(builder, input.Profile)
1✔
73
}
74

75
func (h configCommandHandler) getOrganization(builder *config.ConfigBuilder, input configCommandInput) (*api.Organization, error) {
1✔
76
        cfg, _ := builder.Build()
1✔
77
        token, err := h.performAuth(input, cfg.Auth)
1✔
78
        if err != nil {
2✔
79
                return nil, err
1✔
80
        }
1✔
81
        if token == nil {
2✔
82
                return nil, nil
1✔
83
        }
1✔
84

85
        organizationId, err := h.getOrganizationIdFromToken(token)
1✔
86
        if err != nil {
2✔
87
                return nil, err
1✔
88
        }
1✔
89
        if organizationId == "" {
1✔
NEW
90
                return nil, nil
×
NEW
91
        }
×
92

93
        spinner := visualization.NewSpinner(h.Logger, "Tenant [loading...]: ")
1✔
94
        defer spinner.Close()
1✔
95
        settings := network.NewHttpClientSettings(input.Debug, input.OperationId, map[string]string{}, getTenantsTimeout, getTenantsMaxAttempts, input.Insecure)
1✔
96
        omsClient := api.NewOmsClient(input.BaseUri, token, *settings, h.Logger)
1✔
97
        return omsClient.GetOrganizationInfo(organizationId)
1✔
98
}
99

100
func (h configCommandHandler) getOrganizationIdFromToken(token *auth.AuthToken) (string, error) {
1✔
101
        jwtInfo, err := auth.NewJwtParser().Parse(token.Value)
1✔
102
        if err != nil {
2✔
103
                return "", err
1✔
104
        }
1✔
105
        return jwtInfo.PrtId, nil
1✔
106
}
107

108
func (h configCommandHandler) performAuth(input configCommandInput, authConfig map[string]interface{}) (*auth.AuthToken, error) {
1✔
109
        authContext := h.newAuthContext(input, authConfig)
1✔
110
        for _, authenticator := range h.Authenticators {
2✔
111
                result := authenticator.Auth(*authContext)
1✔
112
                if result.Error != nil {
2✔
113
                        return nil, result.Error
1✔
114
                }
1✔
115
                if result.Token != nil {
2✔
116
                        return result.Token, nil
1✔
117
                }
1✔
118
        }
119
        return nil, nil
1✔
120
}
121

122
func (h configCommandHandler) newAuthContext(input configCommandInput, authConfig map[string]interface{}) *auth.AuthenticatorContext {
1✔
123
        authRequest := auth.NewAuthenticatorRequest(input.BaseUri.String(), map[string]string{})
1✔
124
        return auth.NewAuthenticatorContext(authConfig, input.IdentityUri, input.OperationId, input.Insecure, input.Debug, *authRequest, h.Logger)
1✔
125
}
1✔
126

127
func (h configCommandHandler) handleAuthError(authErr error, builder *config.ConfigBuilder, reader *bufio.Reader, profileName string, authType string) error {
1✔
128
        errorMessageHint := ""
1✔
129
        switch authType {
1✔
130
        case config.AuthTypeLogin:
1✔
131
                errorMessageHint = "Please make sure the login information is correct or enter the organization and tenant information manually."
1✔
132
        default:
1✔
133
                errorMessageHint = "Please make sure the credentials are correct or enter the organization and tenant information manually."
1✔
134
        }
135
        h.Logger.LogError(fmt.Sprintf(`
1✔
136
Authentication Failed!
1✔
137
%v
1✔
138
%s
1✔
139

1✔
140
`, authErr, errorMessageHint))
1✔
141
        err := h.readOrgTenantInput(builder, reader)
1✔
142
        if err != nil {
2✔
143
                return nil
1✔
144
        }
1✔
145
        return h.updateConfigIfNeeded(builder, profileName)
1✔
146
}
147

148
func (h configCommandHandler) readAuthInput(builder *config.ConfigBuilder, reader *bufio.Reader, authType string) error {
1✔
149
        switch authType {
1✔
150
        case config.AuthTypeCredentials:
1✔
151
                return h.readCredentialsInput(builder, reader)
1✔
152
        case config.AuthTypeLogin:
1✔
153
                return h.readLoginInput(builder, reader)
1✔
154
        case config.AuthTypePat:
1✔
155
                return h.readPatInput(builder, reader)
1✔
156
        case "":
1✔
157
                return nil
1✔
158
        }
NEW
159
        return fmt.Errorf("Invalid auth, supported values: %s, %s, %s", config.AuthTypeCredentials, config.AuthTypeLogin, config.AuthTypePat)
×
160
}
161

162
func (h configCommandHandler) updateConfigIfNeeded(builder *config.ConfigBuilder, profileName string) error {
1✔
163
        updatedConfig, changed := builder.Build()
1✔
164
        if !changed {
2✔
165
                return nil
1✔
166
        }
1✔
167

168
        err := h.ConfigProvider.Update(profileName, updatedConfig)
1✔
169
        if err != nil {
1✔
170
                return err
×
171
        }
×
172
        _, _ = fmt.Fprintln(h.StdOut, successfullyConfiguredMessage)
1✔
173
        return nil
1✔
174
}
175

176
func (h configCommandHandler) getOrCreateProfile(profileName string) config.Config {
1✔
177
        cfg := h.ConfigProvider.Config(profileName)
1✔
178
        if cfg == nil {
2✔
179
                return h.ConfigProvider.New()
1✔
180
        }
1✔
181
        return *cfg
1✔
182
}
183

184
func (h configCommandHandler) getDisplayValue(value string, masked bool) string {
1✔
185
        if value == "" {
2✔
186
                return notSetMessage
1✔
187
        }
1✔
188
        if masked {
2✔
189
                return h.maskValue(value)
1✔
190
        }
1✔
191
        return value
1✔
192
}
193

194
func (h configCommandHandler) maskValue(value string) string {
1✔
195
        if len(value) < 10 {
2✔
196
                return maskMessage
1✔
197
        }
1✔
198
        return maskMessage + value[len(value)-4:]
1✔
199
}
200

201
func (h configCommandHandler) readUserInput(message string, reader *bufio.Reader) (*string, error) {
1✔
202
        _, err := fmt.Fprint(h.StdOut, message+" ")
1✔
203
        if err != nil {
1✔
204
                return nil, err
×
205
        }
×
206
        value, err := reader.ReadString('\n')
1✔
207
        if err != nil {
2✔
208
                return nil, err
1✔
209
        }
1✔
210
        value = strings.Trim(value, "\r\n")
1✔
211
        if value == "" {
2✔
212
                return nil, nil
1✔
213
        }
1✔
214
        value = strings.Trim(value, " \t")
1✔
215
        return &value, nil
1✔
216
}
217

218
func (h configCommandHandler) readOrgTenantInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
219
        cfg := builder.Config
1✔
220
        message := fmt.Sprintf("Enter organization [%s]:", h.getDisplayValue(cfg.Organization, false))
1✔
221
        organization, err := h.readUserInput(message, reader)
1✔
222
        if err != nil {
2✔
223
                return err
1✔
224
        }
1✔
225

226
        message = fmt.Sprintf("Enter tenant [%s]:", h.getDisplayValue(cfg.Tenant, false))
1✔
227
        tenant, err := h.readUserInput(message, reader)
1✔
228
        if err != nil {
2✔
229
                return err
1✔
230
        }
1✔
231

232
        builder.
1✔
233
                WithOrganization(organization).
1✔
234
                WithTenant(tenant)
1✔
235
        return nil
1✔
236
}
237

238
func (h configCommandHandler) readCredentialsInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
239
        cfg := builder.Config
1✔
240
        message := fmt.Sprintf("Enter client id [%s]:", h.getDisplayValue(cfg.ClientId(), true))
1✔
241
        clientId, err := h.readUserInput(message, reader)
1✔
242
        if err != nil {
1✔
243
                return err
×
244
        }
×
245

246
        message = fmt.Sprintf("Enter client secret [%s]:", h.getDisplayValue(cfg.ClientSecret(), true))
1✔
247
        clientSecret, err := h.readUserInput(message, reader)
1✔
248
        if err != nil {
1✔
UNCOV
249
                return err
×
UNCOV
250
        }
×
251

252
        builder.WithCredentials(clientId, clientSecret)
1✔
253
        return nil
1✔
254
}
255

256
func (h configCommandHandler) readLoginInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
257
        cfg := builder.Config
1✔
258
        message := fmt.Sprintf("Enter client id [%s]:", h.getDisplayValue(cfg.ClientId(), true))
1✔
259
        clientId, err := h.readUserInput(message, reader)
1✔
260
        if err != nil {
1✔
261
                return err
×
262
        }
×
263
        message = fmt.Sprintf("Enter client secret (only for confidential apps) [%s]:", h.getDisplayValue(cfg.ClientSecret(), true))
1✔
264
        clientSecret, err := h.readUserInput(message, reader)
1✔
265
        if err != nil {
1✔
266
                return err
×
267
        }
×
268
        message = fmt.Sprintf("Enter redirect uri [%s]:", h.getDisplayValue(cfg.RedirectUri(), false))
1✔
269
        redirectUri, err := h.readUserInput(message, reader)
1✔
270
        if err != nil {
1✔
UNCOV
271
                return err
×
UNCOV
272
        }
×
273
        message = fmt.Sprintf("Enter scopes [%s]:", h.getDisplayValue(cfg.Scopes(), false))
1✔
274
        scopes, err := h.readUserInput(message, reader)
1✔
275
        if err != nil {
1✔
UNCOV
276
                return err
×
UNCOV
277
        }
×
278

279
        builder.WithLogin(clientId, clientSecret, redirectUri, scopes)
1✔
280
        return nil
1✔
281
}
282

283
func (h configCommandHandler) readPatInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
284
        cfg := builder.Config
1✔
285
        message := fmt.Sprintf("Enter personal access token [%s]:", h.getDisplayValue(cfg.Pat(), true))
1✔
286
        pat, err := h.readUserInput(message, reader)
1✔
287
        if err != nil {
1✔
UNCOV
288
                return err
×
UNCOV
289
        }
×
290

291
        builder.WithPat(pat)
1✔
292
        return nil
1✔
293
}
294

295
func (h configCommandHandler) readAuthTypeInput(builder *config.ConfigBuilder, reader *bufio.Reader) string {
1✔
296
        authType := builder.Config.AuthType()
1✔
297
        options := []string{
1✔
298
                "credentials - Client Id and Client Secret",
1✔
299
                "login - OAuth login using the browser",
1✔
300
                "pat - Personal Access Token",
1✔
301
        }
1✔
302
        i, _, err := h.readInput(reader, "Authentication type", authType, options)
1✔
303
        if err != nil {
1✔
NEW
304
                return ""
×
NEW
305
        }
×
306
        switch i {
1✔
307
        case 0:
1✔
308
                return config.AuthTypeCredentials
1✔
309
        case 1:
1✔
310
                return config.AuthTypeLogin
1✔
311
        case 2:
1✔
312
                return config.AuthTypePat
1✔
313
        default:
1✔
314
                return authType
1✔
315
        }
316
}
317

318
func (h configCommandHandler) readTenantInput(builder *config.ConfigBuilder, reader *bufio.Reader, organization api.Organization) error {
1✔
319
        tenants := []string{}
1✔
320
        for _, tenant := range organization.Tenants {
2✔
321
                tenants = append(tenants, tenant.Name)
1✔
322
        }
1✔
323
        sort.Strings(tenants)
1✔
324
        i, selected, err := h.readInput(reader, "Tenant", builder.Config.Tenant, tenants)
1✔
325
        if err != nil {
1✔
NEW
326
                return err
×
NEW
327
        }
×
328

329
        if i >= 0 {
2✔
330
                builder.
1✔
331
                        WithOrganization(&organization.Name).
1✔
332
                        WithTenant(&selected)
1✔
333
        }
1✔
334
        return nil
1✔
335
}
336

337
func (h configCommandHandler) readInput(reader *bufio.Reader, message string, value string, options []string) (int, string, error) {
1✔
338
        optionList := ""
1✔
339
        for i, option := range options {
2✔
340
                optionList += fmt.Sprintf("  [%d] %s\n", i+1, option)
1✔
341
        }
1✔
342

343
        for {
2✔
344
                message := fmt.Sprintf(`%s [%s]:
1✔
345
%sSelect:`, message, h.getDisplayValue(value, false), optionList)
1✔
346
                input, err := h.readUserInput(message, reader)
1✔
347
                if err != nil {
1✔
NEW
348
                        return -1, "", err
×
349
                }
×
350
                if input == nil {
2✔
351
                        return -1, "", nil
1✔
352
                }
1✔
353
                i, err := strconv.Atoi(*input)
1✔
354
                if err == nil && i <= len(options) {
2✔
355
                        selectedIndex := i - 1
1✔
356
                        selectedValue := options[selectedIndex]
1✔
357
                        return selectedIndex, selectedValue, nil
1✔
358
                }
1✔
359
        }
360
}
361

362
func newConfigCommandHandler(
363
        input stream.Stream,
364
        stdOut io.Writer,
365
        logger log.Logger,
366
        configProvider config.ConfigProvider,
367
        authenticators []auth.Authenticator,
368
) *configCommandHandler {
1✔
369
        return &configCommandHandler{
1✔
370
                Input:          input,
1✔
371
                StdOut:         stdOut,
1✔
372
                Logger:         logger,
1✔
373
                ConfigProvider: configProvider,
1✔
374
                Authenticators: authenticators,
1✔
375
        }
1✔
376
}
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

© 2025 Coveralls, Inc