blob: 0fcfb2a5025c6da15585b52becbab7aee9703466 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package commands
import (
"bufio"
"encoding/base64"
"errors"
"fmt"
"net/http"
"os"
"strings"
"syscall"
"github.com/apache/brooklyn-client/cli/api/version"
"github.com/apache/brooklyn-client/cli/command_metadata"
"github.com/apache/brooklyn-client/cli/error_handler"
"github.com/apache/brooklyn-client/cli/io"
"github.com/apache/brooklyn-client/cli/net"
"github.com/apache/brooklyn-client/cli/scope"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/ssh/terminal"
)
const authorizationParam = "authorization"
const skipSslChecksParam = "skipSslChecks"
const BASIC_AUTH = "Basic"
const BEARER_AUTH = "Bearer"
type Login struct {
network *net.Network
config *io.Config
brooklynUser string
brooklynPass string
}
func NewLogin(network *net.Network, config *io.Config) (cmd *Login) {
cmd = new(Login)
cmd.network = network
cmd.config = config
return
}
func (cmd *Login) Metadata() command_metadata.CommandMetadata {
return command_metadata.CommandMetadata{
Name: "login",
Description: "Login to brooklyn",
Usage: "BROOKLYN_NAME login URL [USER [PASSWORD]]",
Flags: []cli.Flag{
&cli.BoolFlag{Name: skipSslChecksParam, Usage: "Skip SSL Checks"},
&cli.StringFlag{Name: authorizationParam + ", A", Usage: "Type of authentication header. Format: 'authorization=Basic'" +
" or 'authorization=Bearer:<JWT-token>'"},
},
}
}
func (cmd *Login) promptAndReadUsername() (username string) {
for username == "" {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Username: ")
user, err := reader.ReadString('\n')
if err != nil {
error_handler.ErrorExit(err)
}
username = strings.TrimSpace(user)
}
return username
}
func (cmd *Login) promptAndReadPassword() (password string) {
fmt.Print("Enter Password: ")
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
error_handler.ErrorExit(err)
}
fmt.Printf("\n")
return string(bytePassword)
}
func (cmd *Login) getCredentialsFromCommandLine() {
// Prompt for username if not supplied
if cmd.brooklynUser == "" {
cmd.brooklynUser = cmd.promptAndReadUsername()
}
// Prompt for password if not supplied (password is not echoed to screen
if cmd.brooklynUser != "" && cmd.brooklynPass == "" {
cmd.brooklynPass = cmd.promptAndReadPassword()
}
cmd.network.Credentials = base64.StdEncoding.EncodeToString([]byte(cmd.brooklynUser + ":" + cmd.brooklynPass))
}
func (cmd *Login) Run(scope scope.Scope, c *cli.Context) {
if !c.Args().Present() {
error_handler.ErrorExit("A URL must be provided as the first argument", error_handler.CLIUsageErrorExitCode)
}
// If an argument was not supplied, it is set to empty string
cmd.network.BrooklynUrl = c.Args().Get(0)
cmd.brooklynUser = c.Args().Get(1)
cmd.brooklynPass = c.Args().Get(2)
cmd.network.SkipSslChecks = c.Bool("skipSslChecks")
//clear credentials
cmd.network.Credentials = ""
authParamValue := c.String(authorizationParam)
if authParamValue != "" {
parts := strings.SplitN(authParamValue, ":", 2)
if len(parts) == 2 && strings.EqualFold(parts[0], BEARER_AUTH) {
cmd.network.AuthorizationType = BEARER_AUTH
cmd.network.Credentials = parts[1]
} else {
cmd.network.AuthorizationType = BASIC_AUTH
}
} else {
cmd.network.AuthorizationType = BASIC_AUTH
}
config := io.GetConfig()
if err := net.VerifyLoginURL(cmd.network); err != nil {
error_handler.ErrorExit(err)
}
// Strip off trailing '/' from URL if present.
if cmd.network.BrooklynUrl[len(cmd.network.BrooklynUrl)-1] == '/' {
if len(cmd.network.BrooklynUrl) == 1 {
error_handler.ErrorExit("URL must not be a single \"/\" character", error_handler.CLIUsageErrorExitCode)
}
cmd.network.BrooklynUrl = cmd.network.BrooklynUrl[0 : len(cmd.network.BrooklynUrl)-1]
}
if cmd.network.BrooklynUrl != "" && cmd.brooklynUser == "" && authParamValue == "" {
// if only target supplied at command line see if it already exists in the config file
if credentials, err := config.GetNetworkCredentialsForTarget(cmd.network.BrooklynUrl); err == nil {
cmd.network.Credentials = credentials
}
if authorizationType, err := config.GetAuthType(cmd.network.BrooklynUrl); err == nil {
cmd.network.AuthorizationType = authorizationType
}
}
if cmd.network.AuthorizationType == BASIC_AUTH && cmd.network.Credentials == "" {
cmd.getCredentialsFromCommandLine()
}
// now persist these credentials to the yaml file
cmd.config.SetNetworkCredentials(cmd.network.BrooklynUrl, cmd.network.Credentials)
cmd.config.SetAuthType(cmd.network.BrooklynUrl, cmd.network.AuthorizationType)
cmd.config.SetSkipSslChecks(cmd.network.SkipSslChecks)
cmd.config.Write()
loginVersion, code, err := version.Version(cmd.network)
if nil != err {
if code == http.StatusUnauthorized {
err = errors.New("Unauthorized")
}
error_handler.ErrorExit(err)
}
fmt.Printf("Connected to Brooklyn version %s at %s\n", loginVersion.Version, cmd.network.BrooklynUrl)
}