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

UiPath / uipathcli / 15180300709

22 May 2025 07:04AM UTC coverage: 90.76% (-0.05%) from 90.808%
15180300709

push

github

thschmitt
Improve configure command by providing tenant list

410 of 448 new or added lines in 17 files covered. (91.52%)

14 existing lines in 3 files now uncovered.

6915 of 7619 relevant lines covered (90.76%)

1.01 hits per line

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

86.29
/commandline/config_command_handler.go
1
package commandline
2

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

11
        "github.com/UiPath/uipathcli/auth"
12
        "github.com/UiPath/uipathcli/config"
13
        "github.com/UiPath/uipathcli/log"
14
        "github.com/UiPath/uipathcli/utils/api"
15
        "github.com/UiPath/uipathcli/utils/network"
16
)
17

18
// configCommandHandler implements command for configuring the CLI interactively.
19
type configCommandHandler struct {
20
        StdIn          io.Reader
21
        StdOut         io.Writer
22
        Logger         log.Logger
23
        ConfigProvider config.ConfigProvider
24
        Authenticators []auth.Authenticator
25
}
26

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

32
const getTenantsTimeout = time.Duration(60) * time.Second
33
const getTenantsMaxAttempts = 3
34

35
func (h configCommandHandler) Configure(input configCommandInput) error {
1✔
36
        cfg := h.getOrCreateProfile(input.Profile)
1✔
37
        reader := bufio.NewReader(h.StdIn)
1✔
38

1✔
39
        builder := config.NewConfigBuilder(cfg)
1✔
40
        err := h.readAuthInput(builder, reader, input.AuthType)
1✔
41
        if err != nil {
1✔
NEW
42
                return nil
×
UNCOV
43
        }
×
44

45
        updatedCfg, _ := builder.Build()
1✔
46

1✔
47
        token, err := h.performAuth(input, updatedCfg)
1✔
48
        if err != nil {
2✔
49
                return h.handleAuthError(err, input.Profile, builder, reader)
1✔
50
        }
1✔
51

52
        organization, err := h.getOrganizationInfo(input, token)
1✔
53
        if err != nil {
2✔
54
                return h.handleAuthError(err, input.Profile, builder, reader)
1✔
55
        }
1✔
56
        if organization == nil {
2✔
57
                err := h.readOrgTenantInput(builder, reader)
1✔
58
                if err != nil {
1✔
NEW
59
                        return nil
×
UNCOV
60
                }
×
61
                return h.updateConfigIfNeeded(builder, input.Profile)
1✔
62
        }
63

64
        err = h.readTenantInput(builder, reader, *organization)
1✔
65
        if err != nil {
1✔
NEW
66
                return nil
×
NEW
67
        }
×
68
        return h.updateConfigIfNeeded(builder, input.Profile)
1✔
69
}
70

71
func (h configCommandHandler) getOrganizationInfo(input configCommandInput, token *auth.AuthToken) (*api.Organization, error) {
1✔
72
        if token == nil {
2✔
73
                return nil, nil
1✔
74
        }
1✔
75
        jwtInfo, err := auth.NewJwtParser().Parse(token.Value)
1✔
76
        if err != nil {
2✔
77
                return nil, err
1✔
78
        }
1✔
79
        if jwtInfo.PartId == "" {
1✔
NEW
80
                return nil, nil
×
UNCOV
81
        }
×
82

83
        settings := network.NewHttpClientSettings(input.Debug, input.OperationId, map[string]string{}, getTenantsTimeout, getTenantsMaxAttempts, input.Insecure)
1✔
84
        omsClient := api.NewOmsClient(input.BaseUri, token, *settings, h.Logger)
1✔
85
        return omsClient.GetOrganizationInfo(jwtInfo.PartId)
1✔
86
}
87

88
func (h configCommandHandler) performAuth(input configCommandInput, cfg config.Config) (*auth.AuthToken, error) {
1✔
89
        authContext := h.newAuthContext(input, cfg.Auth.Config)
1✔
90
        for _, authenticator := range h.Authenticators {
2✔
91
                result := authenticator.Auth(*authContext)
1✔
92
                if result.Error != nil {
2✔
93
                        return nil, result.Error
1✔
94
                }
1✔
95
                if result.Token != nil {
2✔
96
                        return result.Token, nil
1✔
97
                }
1✔
98
        }
99
        return nil, nil
1✔
100
}
101

102
func (h configCommandHandler) newAuthContext(input configCommandInput, authConfig map[string]interface{}) *auth.AuthenticatorContext {
1✔
103
        authRequest := auth.NewAuthenticatorRequest(input.BaseUri.String(), map[string]string{})
1✔
104
        return auth.NewAuthenticatorContext(authConfig, input.IdentityUri, input.OperationId, input.Insecure, input.Debug, *authRequest, h.Logger)
1✔
105
}
1✔
106

107
func (h configCommandHandler) handleAuthError(authErr error, profileName string, builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
108
        h.Logger.LogError(fmt.Sprintf(`Could not retrieve organization details: %v
1✔
109
        
1✔
110
Please make sure the credentials are correct or enter the organization and tenant information manually:
1✔
111
`, authErr))
1✔
112
        err := h.readOrgTenantInput(builder, reader)
1✔
113
        if err != nil {
2✔
114
                return nil
1✔
115
        }
1✔
116
        return h.updateConfigIfNeeded(builder, profileName)
1✔
117
}
118

119
func (h configCommandHandler) readAuthInput(builder *config.ConfigBuilder, reader *bufio.Reader, authType string) error {
1✔
120
        if authType == "" {
2✔
121
                authType = h.readAuthTypeInput(builder.Config, reader)
1✔
122
        }
1✔
123
        switch authType {
1✔
124
        case config.AuthTypeCredentials:
1✔
125
                return h.readCredentialsInput(builder, reader)
1✔
126
        case config.AuthTypeLogin:
1✔
127
                return h.readLoginInput(builder, reader)
1✔
128
        case config.AuthTypePat:
1✔
129
                return h.readPatInput(builder, reader)
1✔
130
        case "":
1✔
131
                return nil
1✔
132
        }
NEW
133
        return fmt.Errorf("Invalid auth, supported values: %s, %s, %s", config.AuthTypeCredentials, config.AuthTypeLogin, config.AuthTypePat)
×
134
}
135

136
func (h configCommandHandler) updateConfigIfNeeded(builder *config.ConfigBuilder, profileName string) error {
1✔
137
        updatedConfig, changed := builder.Build()
1✔
138
        if !changed {
2✔
139
                return nil
1✔
140
        }
1✔
141

142
        err := h.ConfigProvider.Update(profileName, updatedConfig)
1✔
143
        if err != nil {
1✔
144
                return err
×
145
        }
×
146
        _, _ = fmt.Fprintln(h.StdOut, successfullyConfiguredMessage)
1✔
147
        return nil
1✔
148
}
149

150
func (h configCommandHandler) getOrCreateProfile(profileName string) config.Config {
1✔
151
        cfg := h.ConfigProvider.Config(profileName)
1✔
152
        if cfg == nil {
2✔
153
                return h.ConfigProvider.New()
1✔
154
        }
1✔
155
        return *cfg
1✔
156
}
157

158
func (h configCommandHandler) getDisplayValue(value string, masked bool) string {
1✔
159
        if value == "" {
2✔
160
                return notSetMessage
1✔
161
        }
1✔
162
        if masked {
2✔
163
                return h.maskValue(value)
1✔
164
        }
1✔
165
        return value
1✔
166
}
167

168
func (h configCommandHandler) maskValue(value string) string {
1✔
169
        if len(value) < 10 {
2✔
170
                return maskMessage
1✔
171
        }
1✔
172
        return maskMessage + value[len(value)-4:]
1✔
173
}
174

175
func (h configCommandHandler) readUserInput(message string, reader *bufio.Reader) (*string, error) {
1✔
176
        _, err := fmt.Fprint(h.StdOut, message+" ")
1✔
177
        if err != nil {
1✔
178
                return nil, err
×
179
        }
×
180
        value, err := reader.ReadString('\n')
1✔
181
        if err != nil {
2✔
182
                return nil, err
1✔
183
        }
1✔
184
        value = strings.Trim(value, "\r\n")
1✔
185
        if value == "" {
2✔
186
                return nil, nil
1✔
187
        }
1✔
188
        value = strings.Trim(value, " \t")
1✔
189
        return &value, nil
1✔
190
}
191

192
func (h configCommandHandler) readOrgTenantInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
193
        cfg := builder.Config
1✔
194
        message := fmt.Sprintf("Enter organization [%s]:", h.getDisplayValue(cfg.Organization, false))
1✔
195
        organization, err := h.readUserInput(message, reader)
1✔
196
        if err != nil {
2✔
197
                return err
1✔
198
        }
1✔
199

200
        message = fmt.Sprintf("Enter tenant [%s]:", h.getDisplayValue(cfg.Tenant, false))
1✔
201
        tenant, err := h.readUserInput(message, reader)
1✔
202
        if err != nil {
2✔
203
                return err
1✔
204
        }
1✔
205

206
        builder.
1✔
207
                WithOrganization(organization).
1✔
208
                WithTenant(tenant)
1✔
209
        return nil
1✔
210
}
211

212
func (h configCommandHandler) readCredentialsInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
213
        cfg := builder.Config
1✔
214
        message := fmt.Sprintf("Enter client id [%s]:", h.getDisplayValue(cfg.ClientId(), true))
1✔
215
        clientId, err := h.readUserInput(message, reader)
1✔
216
        if err != nil {
1✔
217
                return err
×
218
        }
×
219

220
        message = fmt.Sprintf("Enter client secret [%s]:", h.getDisplayValue(cfg.ClientSecret(), true))
1✔
221
        clientSecret, err := h.readUserInput(message, reader)
1✔
222
        if err != nil {
1✔
UNCOV
223
                return err
×
UNCOV
224
        }
×
225

226
        builder.WithCredentials(clientId, clientSecret)
1✔
227
        return nil
1✔
228
}
229

230
func (h configCommandHandler) readLoginInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
231
        cfg := builder.Config
1✔
232
        message := fmt.Sprintf("Enter client id [%s]:", h.getDisplayValue(cfg.ClientId(), true))
1✔
233
        clientId, err := h.readUserInput(message, reader)
1✔
234
        if err != nil {
1✔
235
                return err
×
236
        }
×
237
        message = fmt.Sprintf("Enter client secret (only for confidential apps) [%s]:", h.getDisplayValue(cfg.ClientSecret(), true))
1✔
238
        clientSecret, err := h.readUserInput(message, reader)
1✔
239
        if err != nil {
1✔
240
                return err
×
241
        }
×
242
        message = fmt.Sprintf("Enter redirect uri [%s]:", h.getDisplayValue(cfg.RedirectUri(), false))
1✔
243
        redirectUri, err := h.readUserInput(message, reader)
1✔
244
        if err != nil {
1✔
UNCOV
245
                return err
×
UNCOV
246
        }
×
247
        message = fmt.Sprintf("Enter scopes [%s]:", h.getDisplayValue(cfg.Scopes(), false))
1✔
248
        scopes, err := h.readUserInput(message, reader)
1✔
249
        if err != nil {
1✔
UNCOV
250
                return err
×
UNCOV
251
        }
×
252

253
        builder.WithLogin(clientId, clientSecret, redirectUri, scopes)
1✔
254
        return nil
1✔
255
}
256

257
func (h configCommandHandler) readPatInput(builder *config.ConfigBuilder, reader *bufio.Reader) error {
1✔
258
        cfg := builder.Config
1✔
259
        message := fmt.Sprintf("Enter personal access token [%s]:", h.getDisplayValue(cfg.Pat(), true))
1✔
260
        pat, err := h.readUserInput(message, reader)
1✔
261
        if err != nil {
1✔
UNCOV
262
                return err
×
UNCOV
263
        }
×
264

265
        builder.WithPat(pat)
1✔
266
        return nil
1✔
267
}
268

269
func (h configCommandHandler) readAuthTypeInput(cfg config.Config, reader *bufio.Reader) string {
1✔
270
        authType := cfg.AuthType()
1✔
271
        for {
2✔
272
                message := fmt.Sprintf(`Authentication type [%s]:
1✔
273
  [1] credentials - Client Id and Client Secret
1✔
274
  [2] login - OAuth login using the browser
1✔
275
  [3] pat - Personal Access Token
1✔
276
Select:`, h.getDisplayValue(authType, false))
1✔
277
                input, err := h.readUserInput(message, reader)
1✔
278
                if err != nil {
1✔
279
                        return ""
×
280
                }
×
281
                if input == nil {
2✔
282
                        return authType
1✔
283
                }
1✔
284
                switch *input {
1✔
285
                case "":
×
286
                case "1":
1✔
287
                        return config.AuthTypeCredentials
1✔
288
                case "2":
1✔
289
                        return config.AuthTypeLogin
1✔
290
                case "3":
1✔
291
                        return config.AuthTypePat
1✔
292
                }
293
        }
294
}
295

296
func (h configCommandHandler) readTenantInput(builder *config.ConfigBuilder, reader *bufio.Reader, organization api.Organization) error {
1✔
297
        tenant := builder.Config.Tenant
1✔
298

1✔
299
        tenantList := ""
1✔
300
        for i, tenant := range organization.Tenants {
2✔
301
                tenantList += fmt.Sprintf("  [%d] %s\n", i+1, tenant.Name)
1✔
302
        }
1✔
303

304
        for {
2✔
305
                message := fmt.Sprintf(`Tenant [%s]:
1✔
306
%sSelect:`, h.getDisplayValue(tenant, false), tenantList)
1✔
307
                input, err := h.readUserInput(message, reader)
1✔
308
                if err != nil {
1✔
NEW
309
                        return err
×
NEW
310
                }
×
311
                if input == nil {
1✔
NEW
312
                        return nil
×
NEW
313
                }
×
314
                i, err := strconv.Atoi(*input)
1✔
315
                if err == nil && i <= len(organization.Tenants) {
2✔
316
                        tenant := organization.Tenants[i-1]
1✔
317
                        builder.
1✔
318
                                WithOrganization(&organization.Name).
1✔
319
                                WithTenant(&tenant.Name)
1✔
320
                        return nil
1✔
321
                }
1✔
322
        }
323
}
324

325
func newConfigCommandHandler(
326
        stdIn io.Reader,
327
        stdOut io.Writer,
328
        logger log.Logger,
329
        configProvider config.ConfigProvider,
330
        authenticators []auth.Authenticator,
331
) *configCommandHandler {
1✔
332
        return &configCommandHandler{
1✔
333
                StdIn:          stdIn,
1✔
334
                StdOut:         stdOut,
1✔
335
                Logger:         logger,
1✔
336
                ConfigProvider: configProvider,
1✔
337
                Authenticators: authenticators,
1✔
338
        }
1✔
339
}
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