github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/utils/config/config.go (about) 1 package config 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "github.com/buger/jsonparser" 9 "github.com/jfrog/jfrog-cli-go/utils/cliutils" 10 "github.com/jfrog/jfrog-cli-go/utils/lock" 11 "github.com/jfrog/jfrog-client-go/artifactory" 12 artifactoryAuth "github.com/jfrog/jfrog-client-go/artifactory/auth" 13 "github.com/jfrog/jfrog-client-go/artifactory/services" 14 "github.com/jfrog/jfrog-client-go/auth" 15 "github.com/jfrog/jfrog-client-go/config" 16 distributionAuth "github.com/jfrog/jfrog-client-go/distribution/auth" 17 "github.com/jfrog/jfrog-client-go/utils" 18 "github.com/jfrog/jfrog-client-go/utils/errorutils" 19 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 20 "github.com/jfrog/jfrog-client-go/utils/log" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "sync" 25 ) 26 27 // This is the default server id. It is used when adding a server config without providing a server ID 28 const ( 29 DefaultServerId = "Default-Server" 30 JfrogConfigFile = "jfrog-cli.conf" 31 JfrogDependencies = "dependencies" 32 ) 33 34 func IsArtifactoryConfExists() (bool, error) { 35 conf, err := readConf() 36 if err != nil { 37 return false, err 38 } 39 return conf.Artifactory != nil && len(conf.Artifactory) > 0, nil 40 } 41 42 func IsMissionControlConfExists() (bool, error) { 43 conf, err := readConf() 44 if err != nil { 45 return false, err 46 } 47 return conf.MissionControl != nil && *conf.MissionControl != MissionControlDetails{}, nil 48 } 49 50 func IsBintrayConfExists() (bool, error) { 51 conf, err := readConf() 52 if err != nil { 53 return false, err 54 } 55 return conf.Bintray != nil, nil 56 } 57 58 func GetArtifactorySpecificConfig(serverId string) (*ArtifactoryDetails, error) { 59 conf, err := readConf() 60 if err != nil { 61 return nil, err 62 } 63 details := conf.Artifactory 64 if details == nil || len(details) == 0 { 65 return new(ArtifactoryDetails), nil 66 } 67 if len(serverId) == 0 { 68 details, err := GetDefaultConfiguredArtifactoryConf(details) 69 return details, errorutils.CheckError(err) 70 } 71 return getArtifactoryConfByServerId(serverId, details) 72 } 73 74 // Returns the default server configuration or error if not found. 75 // Caller should perform the check error if required. 76 func GetDefaultConfiguredArtifactoryConf(configs []*ArtifactoryDetails) (*ArtifactoryDetails, error) { 77 if len(configs) == 0 { 78 details := new(ArtifactoryDetails) 79 details.IsDefault = true 80 return details, nil 81 } 82 for _, conf := range configs { 83 if conf.IsDefault == true { 84 return conf, nil 85 } 86 } 87 return nil, errors.New("Couldn't find default server.") 88 } 89 90 // Returns default artifactory conf. Returns nil if default server doesn't exists. 91 func GetDefaultArtifactoryConf() (*ArtifactoryDetails, error) { 92 configurations, err := GetAllArtifactoryConfigs() 93 if err != nil { 94 return nil, err 95 } 96 97 if len(configurations) == 0 { 98 log.Debug("No servers were configured.") 99 return nil, err 100 } 101 102 return GetDefaultConfiguredArtifactoryConf(configurations) 103 } 104 105 // Returns the configured server or error if the server id not found 106 func GetArtifactoryConf(serverId string) (*ArtifactoryDetails, error) { 107 configs, err := GetAllArtifactoryConfigs() 108 if err != nil { 109 return nil, err 110 } 111 return getArtifactoryConfByServerId(serverId, configs) 112 } 113 114 // Returns the configured server or error if the server id not found 115 func getArtifactoryConfByServerId(serverId string, configs []*ArtifactoryDetails) (*ArtifactoryDetails, error) { 116 for _, conf := range configs { 117 if conf.ServerId == serverId { 118 return conf, nil 119 } 120 } 121 return nil, errorutils.CheckError(errors.New(fmt.Sprintf("Server ID '%s' does not exist.", serverId))) 122 } 123 124 func GetAndRemoveConfiguration(serverName string, configs []*ArtifactoryDetails) (*ArtifactoryDetails, []*ArtifactoryDetails) { 125 for i, conf := range configs { 126 if conf.ServerId == serverName { 127 configs = append(configs[:i], configs[i+1:]...) 128 return conf, configs 129 } 130 } 131 return nil, configs 132 } 133 134 func GetAllArtifactoryConfigs() ([]*ArtifactoryDetails, error) { 135 conf, err := readConf() 136 if err != nil { 137 return nil, err 138 } 139 details := conf.Artifactory 140 if details == nil { 141 return make([]*ArtifactoryDetails, 0), nil 142 } 143 return details, nil 144 } 145 146 func ReadMissionControlConf() (*MissionControlDetails, error) { 147 conf, err := readConf() 148 if err != nil { 149 return nil, err 150 } 151 details := conf.MissionControl 152 if details == nil { 153 return new(MissionControlDetails), nil 154 } 155 return details, nil 156 } 157 158 func ReadBintrayConf() (*BintrayDetails, error) { 159 conf, err := readConf() 160 if err != nil { 161 return nil, err 162 } 163 details := conf.Bintray 164 if details == nil { 165 return new(BintrayDetails), nil 166 } 167 return details, nil 168 } 169 170 func SaveArtifactoryConf(details []*ArtifactoryDetails) error { 171 conf, err := readConf() 172 if err != nil { 173 return err 174 } 175 conf.Artifactory = details 176 return saveConfig(conf) 177 } 178 179 func SaveMissionControlConf(details *MissionControlDetails) error { 180 conf, err := readConf() 181 if err != nil { 182 return err 183 } 184 conf.MissionControl = details 185 return saveConfig(conf) 186 } 187 188 func SaveBintrayConf(details *BintrayDetails) error { 189 config, err := readConf() 190 if err != nil { 191 return err 192 } 193 config.Bintray = details 194 return saveConfig(config) 195 } 196 197 func saveConfig(config *ConfigV1) error { 198 config.Version = cliutils.GetConfigVersion() 199 b, err := json.Marshal(&config) 200 if err != nil { 201 return errorutils.CheckError(err) 202 } 203 var content bytes.Buffer 204 err = json.Indent(&content, b, "", " ") 205 if err != nil { 206 return errorutils.CheckError(err) 207 } 208 path, err := getConfFilePath() 209 if err != nil { 210 return err 211 } 212 213 err = ioutil.WriteFile(path, []byte(content.String()), 0600) 214 if err != nil { 215 return errorutils.CheckError(err) 216 } 217 218 return nil 219 } 220 221 func readConf() (*ConfigV1, error) { 222 confFilePath, err := getConfFilePath() 223 if err != nil { 224 return nil, err 225 } 226 config := new(ConfigV1) 227 exists, err := fileutils.IsFileExists(confFilePath, false) 228 if err != nil { 229 return nil, err 230 } 231 if !exists { 232 return config, nil 233 } 234 content, err := fileutils.ReadFile(confFilePath) 235 if err != nil { 236 return nil, err 237 } 238 if len(content) == 0 { 239 return new(ConfigV1), nil 240 } 241 content, err = convertIfNecessary(content) 242 err = json.Unmarshal(content, &config) 243 return config, err 244 } 245 246 // The configuration schema can change between versions, therefore we need to convert old versions to the new schema. 247 func convertIfNecessary(content []byte) ([]byte, error) { 248 version, err := jsonparser.GetString(content, "Version") 249 if err != nil { 250 if err.Error() == "Key path not found" { 251 version = "0" 252 } else { 253 return nil, errorutils.CheckError(err) 254 } 255 } 256 switch version { 257 case "0": 258 result := new(ConfigV1) 259 configV0 := new(ConfigV0) 260 err = json.Unmarshal(content, &configV0) 261 if errorutils.CheckError(err) != nil { 262 return nil, err 263 } 264 result = configV0.Convert() 265 err = saveConfig(result) 266 content, err = json.Marshal(&result) 267 } 268 return content, err 269 } 270 271 func GetJfrogDependenciesPath() (string, error) { 272 dependenciesDir := os.Getenv(cliutils.DependenciesDir) 273 if dependenciesDir != "" { 274 return utils.AddTrailingSlashIfNeeded(dependenciesDir), nil 275 } 276 jfrogHome, err := cliutils.GetJfrogHomeDir() 277 if err != nil { 278 return "", err 279 } 280 return filepath.Join(jfrogHome, JfrogDependencies), nil 281 } 282 283 func getConfFilePath() (string, error) { 284 confPath, err := cliutils.GetJfrogHomeDir() 285 if err != nil { 286 return "", err 287 } 288 os.MkdirAll(confPath, 0777) 289 return filepath.Join(confPath, JfrogConfigFile), nil 290 } 291 292 type ConfigV1 struct { 293 Artifactory []*ArtifactoryDetails `json:"artifactory"` 294 Bintray *BintrayDetails `json:"bintray,omitempty"` 295 MissionControl *MissionControlDetails `json:"MissionControl,omitempty"` 296 Version string `json:"Version,omitempty"` 297 } 298 299 type ConfigV0 struct { 300 Artifactory *ArtifactoryDetails `json:"artifactory,omitempty"` 301 Bintray *BintrayDetails `json:"bintray,omitempty"` 302 MissionControl *MissionControlDetails `json:"MissionControl,omitempty"` 303 } 304 305 func (o *ConfigV0) Convert() *ConfigV1 { 306 config := new(ConfigV1) 307 config.Bintray = o.Bintray 308 config.MissionControl = o.MissionControl 309 if o.Artifactory != nil { 310 o.Artifactory.IsDefault = true 311 o.Artifactory.ServerId = DefaultServerId 312 config.Artifactory = []*ArtifactoryDetails{o.Artifactory} 313 } 314 return config 315 } 316 317 type ArtifactoryDetails struct { 318 Url string `json:"url,omitempty"` 319 SshUrl string `json:"-"` 320 DistributionUrl string `json:"distributionUrl,omitempty"` 321 User string `json:"user,omitempty"` 322 Password string `json:"password,omitempty"` 323 SshKeyPath string `json:"sshKeyPath,omitempty"` 324 SshPassphrase string `json:"SshPassphrase,omitempty"` 325 SshAuthHeaders map[string]string `json:"SshAuthHeaders,omitempty"` 326 AccessToken string `json:"accessToken,omitempty"` 327 RefreshToken string `json:"refreshToken,omitempty"` 328 TokenRefreshInterval int `json:"tokenRefreshInterval,omitempty"` 329 ClientCertPath string `json:"clientCertPath,omitempty"` 330 ClientCertKeyPath string `json:"clientCertKeyPath,omitempty"` 331 ServerId string `json:"serverId,omitempty"` 332 IsDefault bool `json:"isDefault,omitempty"` 333 InsecureTls bool `json:"-"` 334 // Deprecated, use password option instead. 335 ApiKey string `json:"apiKey,omitempty"` 336 } 337 338 type BintrayDetails struct { 339 ApiUrl string `json:"-"` 340 DownloadServerUrl string `json:"-"` 341 User string `json:"user,omitempty"` 342 Key string `json:"key,omitempty"` 343 DefPackageLicense string `json:"defPackageLicense,omitempty"` 344 } 345 346 type MissionControlDetails struct { 347 Url string `json:"url,omitempty"` 348 AccessToken string `json:"accessToken,omitempty"` 349 } 350 351 func (artifactoryDetails *ArtifactoryDetails) IsEmpty() bool { 352 return len(artifactoryDetails.Url) == 0 353 } 354 355 func (artifactoryDetails *ArtifactoryDetails) SetApiKey(apiKey string) { 356 artifactoryDetails.ApiKey = apiKey 357 } 358 359 func (artifactoryDetails *ArtifactoryDetails) SetUser(username string) { 360 artifactoryDetails.User = username 361 } 362 363 func (artifactoryDetails *ArtifactoryDetails) SetPassword(password string) { 364 artifactoryDetails.Password = password 365 } 366 367 func (artifactoryDetails *ArtifactoryDetails) SetAccessToken(accessToken string) { 368 artifactoryDetails.AccessToken = accessToken 369 } 370 371 func (artifactoryDetails *ArtifactoryDetails) SetRefreshToken(refreshToken string) { 372 artifactoryDetails.RefreshToken = refreshToken 373 } 374 375 func (artifactoryDetails *ArtifactoryDetails) SetClientCertPath(certificatePath string) { 376 artifactoryDetails.ClientCertPath = certificatePath 377 } 378 379 func (artifactoryDetails *ArtifactoryDetails) SetClientCertKeyPath(certificatePath string) { 380 artifactoryDetails.ClientCertKeyPath = certificatePath 381 } 382 383 func (artifactoryDetails *ArtifactoryDetails) GetApiKey() string { 384 return artifactoryDetails.ApiKey 385 } 386 387 func (artifactoryDetails *ArtifactoryDetails) GetUrl() string { 388 return artifactoryDetails.Url 389 } 390 391 func (artifactoryDetails *ArtifactoryDetails) GetDistributionUrl() string { 392 return artifactoryDetails.DistributionUrl 393 } 394 395 func (artifactoryDetails *ArtifactoryDetails) GetUser() string { 396 return artifactoryDetails.User 397 } 398 399 func (artifactoryDetails *ArtifactoryDetails) GetPassword() string { 400 return artifactoryDetails.Password 401 } 402 403 func (artifactoryDetails *ArtifactoryDetails) GetAccessToken() string { 404 return artifactoryDetails.AccessToken 405 } 406 407 func (artifactoryDetails *ArtifactoryDetails) GetRefreshToken() string { 408 return artifactoryDetails.RefreshToken 409 } 410 411 func (artifactoryDetails *ArtifactoryDetails) GetClientCertPath() string { 412 return artifactoryDetails.ClientCertPath 413 } 414 415 func (artifactoryDetails *ArtifactoryDetails) GetClientCertKeyPath() string { 416 return artifactoryDetails.ClientCertKeyPath 417 } 418 419 func (artifactoryDetails *ArtifactoryDetails) SshAuthHeaderSet() bool { 420 return len(artifactoryDetails.SshAuthHeaders) > 0 421 } 422 423 func (artifactoryDetails *ArtifactoryDetails) CreateArtAuthConfig() (auth.CommonDetails, error) { 424 artAuth := artifactoryAuth.NewArtifactoryDetails() 425 artAuth.SetUrl(artifactoryDetails.Url) 426 return artifactoryDetails.createArtAuthConfig(artAuth) 427 } 428 429 func (artifactoryDetails *ArtifactoryDetails) CreateDistAuthConfig() (auth.CommonDetails, error) { 430 artAuth := distributionAuth.NewDistributionDetails() 431 artAuth.SetUrl(artifactoryDetails.DistributionUrl) 432 return artifactoryDetails.createArtAuthConfig(artAuth) 433 } 434 435 func (artifactoryDetails *ArtifactoryDetails) createArtAuthConfig(commonDetails auth.CommonDetails) (auth.CommonDetails, error) { 436 commonDetails.SetSshUrl(artifactoryDetails.SshUrl) 437 commonDetails.SetSshAuthHeaders(artifactoryDetails.SshAuthHeaders) 438 commonDetails.SetAccessToken(artifactoryDetails.AccessToken) 439 // If refresh token is not empty, set a refresh handler and skip other credentials 440 if artifactoryDetails.RefreshToken != "" { 441 tokenRefreshServerId = artifactoryDetails.ServerId 442 commonDetails.SetTokenRefreshHandler(TokenRefreshHandler) 443 } else { 444 commonDetails.SetApiKey(artifactoryDetails.ApiKey) 445 commonDetails.SetUser(artifactoryDetails.User) 446 commonDetails.SetPassword(artifactoryDetails.Password) 447 } 448 commonDetails.SetClientCertPath(artifactoryDetails.ClientCertPath) 449 commonDetails.SetClientCertKeyPath(artifactoryDetails.ClientCertKeyPath) 450 commonDetails.SetSshKeyPath(artifactoryDetails.SshKeyPath) 451 commonDetails.SetSshPassphrase(artifactoryDetails.SshPassphrase) 452 if commonDetails.IsSshAuthentication() && !commonDetails.IsSshAuthHeaderSet() { 453 err := commonDetails.AuthenticateSsh(artifactoryDetails.SshKeyPath, artifactoryDetails.SshPassphrase) 454 if err != nil { 455 return nil, err 456 } 457 } 458 return commonDetails, nil 459 } 460 461 func (missionControlDetails *MissionControlDetails) GetAccessToken() string { 462 return missionControlDetails.AccessToken 463 } 464 465 func (missionControlDetails *MissionControlDetails) SetAccessToken(accessToken string) { 466 missionControlDetails.AccessToken = accessToken 467 } 468 469 // Internal golang locking for the same process. 470 var mutex sync.Mutex 471 var tokenRefreshServerId string 472 473 func TokenRefreshHandler(currentAccessToken string) (newAccessToken string, err error) { 474 mutex.Lock() 475 lockFile, err := lock.CreateLock() 476 defer mutex.Unlock() 477 defer lockFile.Unlock() 478 if err != nil { 479 return "", err 480 } 481 482 serverConfiguration, err := GetArtifactoryConf(tokenRefreshServerId) 483 if err != nil { 484 return "", nil 485 } 486 // Token already refreshed 487 if serverConfiguration.AccessToken != "" && serverConfiguration.AccessToken != currentAccessToken { 488 return serverConfiguration.AccessToken, nil 489 } 490 491 refreshToken := serverConfiguration.RefreshToken 492 // Remove previous tokens 493 serverConfiguration.AccessToken = "" 494 serverConfiguration.RefreshToken = "" 495 // Try refreshing tokens 496 newToken, err := RefreshExpiredToken(serverConfiguration, currentAccessToken, refreshToken) 497 498 if err != nil { 499 log.Debug("Refresh token failed: " + err.Error()) 500 log.Debug("Trying to create new tokens...") 501 502 expirySeconds, err := auth.ExtractExpiryFromAccessToken(currentAccessToken) 503 if err != nil { 504 return "", err 505 } 506 507 newToken, err = CreateTokensForConfig(serverConfiguration, expirySeconds) 508 if err != nil { 509 return "", nil 510 } 511 } 512 513 err = writeNewTokens(serverConfiguration, tokenRefreshServerId, newToken.AccessToken, newToken.RefreshToken) 514 if err != nil { 515 log.Error("Failed writing new tokens to config after handling access token expiry: " + err.Error()) 516 } 517 return newToken.AccessToken, nil 518 } 519 520 func writeNewTokens(serverConfiguration *ArtifactoryDetails, serverId, accessToken, refreshToken string) error { 521 serverConfiguration.SetAccessToken(accessToken) 522 serverConfiguration.SetRefreshToken(refreshToken) 523 524 // Get configurations list 525 configurations, err := GetAllArtifactoryConfigs() 526 if err != nil { 527 return err 528 } 529 530 // Remove and get the server details from the configurations list 531 _, configurations = GetAndRemoveConfiguration(serverId, configurations) 532 533 // Append the configuration to the configurations list 534 configurations = append(configurations, serverConfiguration) 535 return SaveArtifactoryConf(configurations) 536 } 537 538 func CreateTokensForConfig(artifactoryDetails *ArtifactoryDetails, expirySeconds int) (services.CreateTokenResponseData, error) { 539 servicesManager, err := CreateTokensServiceManager(artifactoryDetails) 540 if err != nil { 541 return services.CreateTokenResponseData{}, err 542 } 543 544 createTokenParams := services.NewCreateTokenParams() 545 createTokenParams.Username = artifactoryDetails.User 546 createTokenParams.ExpiresIn = expirySeconds 547 // User-scoped token 548 createTokenParams.Scope = "member-of-groups:*" 549 createTokenParams.Refreshable = true 550 551 newToken, err := servicesManager.CreateToken(createTokenParams) 552 if err != nil { 553 return services.CreateTokenResponseData{}, err 554 } 555 return newToken, nil 556 } 557 558 func CreateInitialRefreshTokensIfNeeded(artifactoryDetails *ArtifactoryDetails) (err error) { 559 if !(artifactoryDetails.TokenRefreshInterval > 0 && artifactoryDetails.RefreshToken == "" && artifactoryDetails.AccessToken == "") { 560 return nil 561 } 562 mutex.Lock() 563 lockFile, err := lock.CreateLock() 564 defer mutex.Unlock() 565 defer lockFile.Unlock() 566 if err != nil { 567 return err 568 } 569 570 newToken, err := CreateTokensForConfig(artifactoryDetails, artifactoryDetails.TokenRefreshInterval*60) 571 if err != nil { 572 return err 573 } 574 // remove initializing value 575 artifactoryDetails.TokenRefreshInterval = 0 576 return writeNewTokens(artifactoryDetails, artifactoryDetails.ServerId, newToken.AccessToken, newToken.RefreshToken) 577 } 578 579 func RefreshExpiredToken(artifactoryDetails *ArtifactoryDetails, currentAccessToken string, refreshToken string) (services.CreateTokenResponseData, error) { 580 // The tokens passed as parameters are also used for authentication 581 noCredsDetails := new(ArtifactoryDetails) 582 noCredsDetails.Url = artifactoryDetails.Url 583 noCredsDetails.ClientCertPath = artifactoryDetails.ClientCertPath 584 noCredsDetails.ClientCertKeyPath = artifactoryDetails.ClientCertKeyPath 585 noCredsDetails.ServerId = artifactoryDetails.ServerId 586 noCredsDetails.IsDefault = artifactoryDetails.IsDefault 587 588 servicesManager, err := CreateTokensServiceManager(noCredsDetails) 589 if err != nil { 590 return services.CreateTokenResponseData{}, err 591 } 592 593 refreshTokenParams := services.NewRefreshTokenParams() 594 refreshTokenParams.AccessToken = currentAccessToken 595 refreshTokenParams.RefreshToken = refreshToken 596 return servicesManager.RefreshToken(refreshTokenParams) 597 } 598 599 func CreateTokensServiceManager(artDetails *ArtifactoryDetails) (*artifactory.ArtifactoryServicesManager, error) { 600 certPath, err := cliutils.GetJfrogSecurityDir() 601 if err != nil { 602 return nil, err 603 } 604 artAuth, err := artDetails.CreateArtAuthConfig() 605 if err != nil { 606 return nil, err 607 } 608 serviceConfig, err := config.NewConfigBuilder(). 609 SetArtDetails(artAuth). 610 SetCertificatesPath(certPath). 611 SetInsecureTls(artDetails.InsecureTls). 612 SetDryRun(false). 613 Build() 614 if err != nil { 615 return nil, err 616 } 617 return artifactory.New(&artAuth, serviceConfig) 618 }