add dcos oauth support (#560)

This commit is contained in:
tamarrow
2016-04-11 21:06:24 -07:00
parent 226dc639be
commit 0ef5482114
30 changed files with 339 additions and 289 deletions

View File

@@ -160,7 +160,6 @@ fi
ENV_SETUP="$VIRTUAL_ENV_PATH/bin/env-setup"
source "$ENV_SETUP"
dcos config set core.email anonymous-optout
dcos config set core.reporting false
dcos config set core.dcos_url $DCOS_URL
dcos config set core.ssl_verify false

View File

@@ -45,30 +45,12 @@ def _segment_track(event, conf, properties):
"""
data = {'event': event,
'properties': properties}
if 'core.email' in conf:
data['userId'] = conf['core.email']
else:
data['anonymousId'] = session_id
'properties': properties,
'anonymousId': session_id}
_segment_request('track', data)
def segment_identify(conf):
"""
Send a segment.io 'identify' event
:param conf: dcos config file
:type conf: Toml
:rtype: None
"""
if 'core.email' in conf:
data = {'userId': conf.get('core.email')}
_segment_request('identify', data)
def _segment_request(path, data):
"""
Send a segment.io HTTP request

View File

108
cli/dcoscli/auth/main.py Normal file
View File

@@ -0,0 +1,108 @@
import dcoscli
import docopt
from dcos import cmds, config, emitting, http, util
from dcos.errors import DCOSAuthorizationException, DCOSException
from dcoscli.subcommand import default_command_info, default_doc
from dcoscli.util import decorate_docopt_usage
from six.moves import urllib
emitter = emitting.FlatEmitter()
logger = util.get_logger(__name__)
def main(argv):
try:
return _main(argv)
except DCOSException as e:
emitter.publish(e)
return 1
@decorate_docopt_usage
def _main(argv):
args = docopt.docopt(
default_doc("auth"),
argv=argv,
version='dcos-auth version {}'.format(dcoscli.version))
http.silence_requests_warnings()
return cmds.execute(_cmds(), args)
def _cmds():
"""
:returns: all the supported commands
:rtype: list of dcos.cmds.Command
"""
return [
cmds.Command(
hierarchy=['auth', 'login'],
arg_keys=[],
function=_login),
cmds.Command(
hierarchy=['auth', 'logout'],
arg_keys=[],
function=_logout),
cmds.Command(
hierarchy=['auth'],
arg_keys=['--info'],
function=_info),
]
def _info(info):
"""
:param info: Whether to output a description of this subcommand
:type info: boolean
:returns: process status
:rtype: int
"""
emitter.publish(default_command_info("auth"))
return 0
def _login():
"""
:returns: process status
:rtype: int
"""
# every call to login will generate a new token if applicable
_logout()
conf = util.get_config()
dcos_url = conf.get("core.dcos_url")
if dcos_url is None:
msg = ("Please provide the url to your DCOS cluster: "
"`dcos config set core.dcos_url`")
raise DCOSException(msg)
# hit protected endpoint which will prompt for auth if cluster has auth
try:
url = urllib.parse.urljoin(dcos_url, 'exhibitor')
http.get(url)
# if the user is authenticated, they have effectively "logged in" even if
# they are not authorized for this endpoint
except DCOSAuthorizationException:
pass
emitter.publish("Login successful!")
return 0
def _logout():
"""
Logout the user from dcos acs auth or oauth
:returns: process status
:rtype: int
"""
if util.get_config().get("core.dcos_acs_token") is not None:
config.unset("core.dcos_acs_token")
return 0

View File

@@ -4,7 +4,6 @@ import dcoscli
import docopt
from dcos import cmds, config, emitting, http, util
from dcos.errors import DCOSException
from dcoscli import analytics
from dcoscli.subcommand import default_command_info, default_doc
from dcoscli.util import decorate_docopt_usage
@@ -88,9 +87,11 @@ def _set(name, value):
notice = ("This config property has been deprecated. "
"Please add your repositories with `dcos package repo add`")
return DCOSException(notice)
toml_config = config.set_val(name, value)
if (name == 'core.reporting' is True) or (name == 'core.email'):
analytics.segment_identify(toml_config)
if name == "core.email":
notice = "This config property has been deprecated."
return DCOSException(notice)
config.set_val(name, value)
return 0

View File

@@ -0,0 +1,21 @@
Description:
Authenticate to DCOS cluster
Usage:
dcos auth --info
dcos auth login
dcos auth logout
Commands:
login
Login to your DCOS Cluster.
logout
Logout of your DCOS Cluster.
Options:
-h, --help
Print usage.
--info
Print a short description of this subcommand.
--version
Print version information.

View File

@@ -5,8 +5,7 @@ from concurrent.futures import ThreadPoolExecutor
import dcoscli
import docopt
from dcos import (auth, constants, emitting, errors, http, mesos, subcommand,
util)
from dcos import constants, emitting, errors, http, mesos, subcommand, util
from dcos.errors import DCOSAuthenticationException, DCOSException
from dcoscli import analytics
from dcoscli.subcommand import SubcommandMain, default_doc
@@ -40,10 +39,6 @@ def _main():
util.configure_process_from_environ()
if args['<command>'] != 'config' and \
not auth.check_if_user_authenticated():
auth.force_auth()
config = util.get_config()
set_ssl_info_env_vars(config)

View File

@@ -11,6 +11,7 @@ def _default_modules():
"""
# avoid circular imports
from dcoscli.auth import main as auth_main
from dcoscli.config import main as config_main
from dcoscli.help import main as help_main
from dcoscli.marathon import main as marathon_main
@@ -19,7 +20,8 @@ def _default_modules():
from dcoscli.service import main as service_main
from dcoscli.task import main as task_main
return {'config': config_main,
return {'auth': auth_main,
'config': config_main,
'help': help_main,
'marathon': marathon_main,
'node': node_main,

View File

@@ -1,4 +1,3 @@
[core]
reporting = false
email = "test@mail.com"
dcos_url = "http://dcos.snakeoil.mesosphere.com"

View File

@@ -1,5 +1,4 @@
[core]
dcos_acs_token = "foobar"
dcos_url = "https://dcos.snakeoil.mesosphere.com"
email = "test@mail.com"
reporting = true

View File

@@ -1,4 +1,3 @@
[core]
email = "test@mail.com"
reporting = true
dcos_url = "http://dcos.snakeoil.mesosphere.com"

View File

@@ -1,3 +1,2 @@
[core]
reporting = false
email = "test@mail.com"

View File

@@ -1,5 +1,4 @@
[core]
reporting = false
email = "test@mail.com"
[package]
cosmos_url = "http://localhost:7070"

View File

@@ -1,6 +1,5 @@
[core]
reporting = false
email = "test@mail.com"
timeout = 5
dcos_url = "http://dcos.snakeoil.mesosphere.com"
ssl_verify = "false"

View File

@@ -0,0 +1,21 @@
Description:
Authenticate to DCOS cluster
Usage:
dcos auth --info
dcos auth login
dcos auth logout
Commands:
login
Login to your DCOS Cluster.
logout
Logout of your DCOS Cluster.
Options:
-h, --help
Print usage.
--info
Print a short description of this subcommand.
--version
Print version information.

View File

@@ -5,6 +5,7 @@ for easy management of a DCOS installation.
Available DCOS commands:
auth Authenticate to DCOS cluster
config Manage the DCOS configuration file
help Display help information about DCOS
marathon Deploy and manage applications to DCOS

View File

@@ -1,6 +1,5 @@
[core]
reporting = false
email = "test@mail.com"
[marathon]
[package]
cosmos_url = "http://localhost:7070"

View File

@@ -3,5 +3,4 @@ cosmos_url = "http://localhost:7070"
[core]
timeout = 5
dcos_url = "https://dcos.snakeoil.mesosphere.com"
email = "test@mail.com"
reporting = false

View File

@@ -510,4 +510,4 @@ def config_unset(key, env=None):
returncode, stdout, stderr = exec_command(cmd, env=env)
assert returncode == 0
assert stderr == b''
assert stdout == b''

View File

@@ -0,0 +1,53 @@
import os
from dcos import constants
import pytest
from .common import assert_command, config_set, exec_command
@pytest.fixture
def env():
r = os.environ.copy()
r.update({
constants.PATH_ENV: os.environ[constants.PATH_ENV],
constants.DCOS_CONFIG_ENV: os.path.join("tests", "data", "dcos.toml"),
})
return r
def test_info():
stdout = b'Authenticate to DCOS cluster\n'
assert_command(['dcos', 'auth', '--info'],
stdout=stdout)
def test_version():
stdout = b'dcos-auth version SNAPSHOT\n'
assert_command(['dcos', 'auth', '--version'],
stdout=stdout)
def test_logout_no_token(env):
exec_command(['dcos', 'config', 'unset', 'core.dcos_acs_token'], env=env)
returncode, _, stderr = exec_command(
['dcos', 'config', 'show', 'core.dcos_acs_token'], env=env)
assert returncode == 1
assert stderr == b"Property 'core.dcos_acs_token' doesn't exist\n"
def test_logout_with_token(env):
config_set('core.dcos_acs_token', "foobar", env=env)
stderr = b"[core.dcos_acs_token]: changed\n"
assert_command(
['dcos', 'config', 'set', 'core.dcos_acs_token', 'faketoken'],
stderr=stderr,
env=env)
stderr = b'Removed [core.dcos_acs_token]\n'
assert_command(['dcos', 'auth', 'logout'],
stderr=stderr,
env=env)

View File

@@ -51,7 +51,6 @@ def test_version():
def _test_list_property(env):
stdout = b"""core.dcos_url=http://dcos.snakeoil.mesosphere.com
core.email=test@mail.com
core.reporting=False
core.ssl_verify=false
core.timeout=5
@@ -89,7 +88,6 @@ def test_get_top_property(env):
b"Property 'core' doesn't fully specify a value - "
b"possible properties are:\n"
b"core.dcos_url\n"
b"core.email\n"
b"core.reporting\n"
b"core.ssl_verify\n"
b"core.timeout\n"
@@ -108,6 +106,13 @@ def test_set_package_sources_property(env):
returncode=1)
def test_set_core_email_property(env):
notice = (b"This config property has been deprecated.\n")
assert_command(['dcos', 'config', 'set', 'core.email', 'foo@bar.com'],
stderr=notice,
returncode=1)
def test_set_existing_string_property(env):
config_set('core.dcos_url',
'http://dcos.snakeoil.mesosphere.com:5081', env)
@@ -175,7 +180,7 @@ def test_unset_missing_property(env):
def test_unset_output(env):
assert_command(['dcos', 'config', 'unset', 'core.reporting'],
stdout=b'Removed [core.reporting]\n',
stderr=b'Removed [core.reporting]\n',
env=env)
config_set('core.reporting', 'false', env)
@@ -185,7 +190,6 @@ def test_unset_top_property(env):
b"Property 'core' doesn't fully specify a value - "
b"possible properties are:\n"
b"core.dcos_url\n"
b"core.email\n"
b"core.reporting\n"
b"core.ssl_verify\n"
b"core.timeout\n"

View File

@@ -26,6 +26,7 @@ for easy management of a DCOS installation.
Available DCOS commands:
\tauth \tAuthenticate to DCOS cluster
\tconfig \tManage the DCOS configuration file
\thelp \tDisplay help information about DCOS
\tmarathon \tDeploy and manage applications to DCOS
@@ -75,3 +76,9 @@ def test_help_task():
with open('tests/data/help/task.txt') as content:
assert_command(['dcos', 'help', 'task'],
stdout=content.read().encode('utf-8'))
def test_help_auth():
with open('tests/data/help/auth.txt') as content:
assert_command(['dcos', 'help', 'auth'],
stdout=content.read().encode('utf-8'))

View File

@@ -4,14 +4,11 @@ from functools import wraps
import dcoscli.analytics
import rollbar
from dcos import constants, http
from dcoscli.constants import SEGMENT_URL
from dcos import constants
from dcoscli.main import main
from dcoscli.subcommand import SubcommandMain
from mock import patch
from .common import mock_called_some_args
ANON_ID = 0
USER_ID = 'test@mail.com'
@@ -32,22 +29,6 @@ def _mock(fn):
return wrapper
@_mock
def test_config_set():
argv = ['set', 'core.email', 'test@mail.com']
config_thread = SubcommandMain("config", argv)
exitcode, err = config_thread.run_and_capture()
assert exitcode == 0
assert err is None
# segment.io
assert mock_called_some_args(http.post,
'{}/identify'.format(SEGMENT_URL),
json={'userId': 'test@mail.com'},
timeout=(1, 1))
@_mock
def test_cluster_id_not_sent_on_config_call():
"""Tests that cluster_id is not sent to segment.io on call to config

View File

@@ -1,56 +0,0 @@
import os
import webbrowser
from dcos import auth, constants, util
from dcoscli.main import main
from mock import Mock, patch
def test_no_browser_auth():
webbrowser.get = Mock(side_effect=webbrowser.Error())
with patch('webbrowser.open') as op:
_mock_dcos_run([util.which('dcos')], False)
assert op.call_count == 0
def test_anonymous_login():
with patch('sys.stdin.readline', return_value='\n'), \
patch('uuid.uuid1', return_value='anonymous@email'):
assert _mock_dcos_run(['dcos', 'help'], False) == 0
assert _mock_dcos_run(['dcos',
'config', 'show', 'core.email'], False) == 0
assert _mock_dcos_run(['dcos',
'config', 'unset', 'core.email'], False) == 0
def _mock_dcos_run(args, authenticated=True):
if authenticated:
env = _config_with_credentials()
else:
env = _config_without_credentials()
with patch('sys.argv', args), patch.dict(os.environ, env):
return main()
def test_when_authenticated():
with patch('dcos.auth.force_auth'):
_mock_dcos_run(['dcos'], True)
assert auth.force_auth.call_count == 0
def _config_with_credentials():
return {
constants.DCOS_CONFIG_ENV: os.path.join(
'tests', 'data', 'auth', 'dcos_with_credentials.toml')
}
def _config_without_credentials():
return {
constants.DCOS_CONFIG_ENV: os.path.join(
'tests', 'data', 'auth', 'dcos_without_credentials.toml')
}

View File

@@ -25,6 +25,14 @@ def test_get_auth_scheme_acs():
assert realm == "acsjwt"
def test_get_auth_scheme_oauth():
with patch('requests.Response') as mock:
mock.headers = {'www-authenticate': 'oauthjwt'}
auth_scheme, realm = http.get_auth_scheme(mock)
assert auth_scheme == "oauthjwt"
assert realm == "oauthjwt"
def test_get_auth_scheme_bad_request():
with patch('requests.Response') as mock:
mock.headers = {'www-authenticate': ''}
@@ -116,6 +124,23 @@ def test_request_with_bad_auth_acl(mock, req_mock, auth_mock):
assert e.exconly().split(':')[1].strip() == "Authentication failed"
@patch('requests.Response')
@patch('dcos.http._request')
@patch('dcos.http._get_http_auth')
def test_request_with_bad_oauth(mock, req_mock, auth_mock):
mock.url = 'http://domain.com'
mock.headers = {'www-authenticate': 'oauthjwt'}
mock.status_code = 401
auth_mock.return_value = http.DCOSAcsAuth("token")
req_mock.return_value = mock
with pytest.raises(DCOSException) as e:
http._request_with_auth(mock, "method", mock.url)
assert e.exconly().split(':')[1].strip() == "Authentication failed"
@patch('requests.Response')
@patch('dcos.http._request')
@patch('dcos.http._get_http_auth')
@@ -152,3 +177,22 @@ def test_request_with_auth_acl(mock, req_mock, auth_mock):
response = http._request_with_auth(mock, "method", mock.url)
assert response.status_code == 200
@patch('requests.Response')
@patch('dcos.http._request')
@patch('dcos.http._get_http_auth')
def test_request_with_auth_oauth(mock, req_mock, auth_mock):
mock.url = 'http://domain.com'
mock.headers = {'www-authenticate': 'oauthjwt'}
mock.status_code = 401
auth = http.DCOSAcsAuth("token")
auth_mock.return_value = auth
mock2 = copy.deepcopy(mock)
mock2.status_code = 200
req_mock.return_value = mock2
response = http._request_with_auth(mock, "method", mock.url)
assert response.status_code == 200

View File

@@ -1,138 +0,0 @@
import json
import sys
import uuid
import pkg_resources
from dcos import config, emitting, errors, http, jsonitem, util
from dcos.errors import DCOSException
from six import iteritems
from oauth2client import client
CLIENT_ID = '6a552732-ab9b-410d-9b7d-d8c6523b09a1'
CLIENT_SECRET = 'f56c1e2b-8599-40ca-b6a0-3aba3e702eae'
AUTH_URL = 'https://accounts.mesosphere.com/oauth/authorize'
TOKEN_URL = 'https://accounts.mesosphere.com/oauth/token'
USER_INFO_URL = 'https://accounts.mesosphere.com/api/v1/user.json'
CORE_TOKEN_KEY = 'token'
CORE_EMAIL_KEY = 'email'
emitter = emitting.FlatEmitter()
logger = util.get_logger(__name__)
def _authorize():
"""Create OAuth flow and authorize user
:return: credentials dict
:rtype: dict
"""
try:
flow = client.OAuth2WebServerFlow(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
scope='',
auth_uri=AUTH_URL,
token_uri=TOKEN_URL,
redirect_uri=client.OOB_CALLBACK_URN,
response_type='code'
)
return _run(flow)
except:
logger.exception('Error during OAuth web flow')
raise DCOSException('There was a problem with '
'web authentication.')
def make_oauth_request(code, flow):
"""Make request to auth server using auth_code.
:param: code: auth_code read from cli
:param: flow: OAuth2 web server flow
:return: dict with the keys token and email
:rtype: dict
"""
credential = flow.step2_exchange(code)
token = credential.access_token
headers = {'Authorization': str('Bearer ' + token)}
data = http.requests.get(USER_INFO_URL, headers=headers).json()
mail = data['email']
credentials = {CORE_TOKEN_KEY: credential.access_token,
CORE_EMAIL_KEY: mail}
return credentials
def _run(flow):
"""Make authorization and retrieve access token and user email.
:param flow: OAuth2 web server flow
:param launch_browser: if possible to run browser
:return: dict with the keys token and email
:rtype: dict
"""
emitter.publish(
errors.DefaultError(
'\n\n\n{}\n\n {}\n\n'.format(
'Go to the following link in your browser:',
flow.step1_get_authorize_url())))
sys.stderr.write('Enter verification code: ')
code = sys.stdin.readline().strip()
if not code:
sys.stderr.write('Skipping authentication.\nEnter email address: ')
email = sys.stdin.readline().strip()
if not email:
emitter.publish(
errors.DefaultError(
'Skipping email input.'))
email = str(uuid.uuid1())
return {CORE_EMAIL_KEY: email}
return make_oauth_request(code, flow)
def check_if_user_authenticated():
"""Check if user is authenticated already
:returns user auth status
:rtype: boolean
"""
dcos_config = util.get_config()
return dcos_config.get('core.email', '') != ''
def force_auth():
"""Make user authentication process
:returns authentication process status
:rtype: boolean
"""
credentials = _authorize()
_save_auth_keys(credentials)
def _save_auth_keys(key_dict):
"""
:param key_dict: auth parameters dict
:type key_dict: dict
:rtype: None
"""
toml_config = util.get_config(True)
section = 'core'
config_schema = json.loads(
pkg_resources.resource_string(
'dcos',
'data/config-schema/core.json').decode('utf-8'))
for k, v in iteritems(key_dict):
python_value = jsonitem.parse_json_value(k, v, config_schema)
name = '{}.{}'.format(section, k)
toml_config[name] = python_value
config.save(toml_config)
return None

View File

@@ -130,7 +130,7 @@ def unset(name):
elif isinstance(value, collections.Mapping):
raise DCOSException(_generate_choice_msg(name, value))
else:
emitter.publish("Removed [{}]".format(name))
emitter.publish(DefaultError("Removed [{}]".format(name)))
save(toml_config)
return

View File

@@ -13,22 +13,12 @@
"title": "DCOS ACS token",
"type": "string"
},
"email": {
"description": "Your email address",
"title": "Your email address",
"type": "string"
},
"mesos_master_url": {
"description": "Mesos master URL. Must be set in format: \"http://host:port\"",
"format": "uri",
"title": "Mesos Master URL",
"type": "string"
},
"refresh_token": {
"description": "Your OAuth refresh token",
"title": "The OAuth refresh token",
"type": "string"
},
"reporting": {
"default": true,
"description": "Whether to report usage events to Mesosphere",
@@ -42,11 +32,6 @@
"title": "Request timeout in seconds",
"type": "integer"
},
"token": {
"description": "Your OAuth access token",
"title": "Your OAuth access token",
"type": "string"
},
"ssl_verify": {
"type": "string",
"default": "false",

View File

@@ -161,9 +161,13 @@ def _request_with_auth(response,
if creds not in AUTH_CREDS and response.status_code == 200:
AUTH_CREDS[creds] = auth
# acs invalid token
elif response.status_code == 401 and auth_scheme == "acsjwt":
elif response.status_code == 401 and \
auth_scheme in ["acsjwt", "oauthjwt"]:
if util.get_config().get("core.dcos_acs_token") is not None:
config.unset("core.dcos_acs_token")
msg = ("Your core.dcos_acs_token is invalid. "
"Please run: `dcos auth login`")
raise DCOSException(msg)
i += 1
@@ -342,7 +346,7 @@ def _get_auth_credentials(username, hostname):
def get_auth_scheme(response):
"""Return authentication scheme and realm requested by server for 'Basic'
or 'acsjwt' (DCOS acs auth) type or None
or 'acsjwt' (DCOS acs auth) or 'oauthjwt' (DCOS acs oauth) type or None
:param response: requests.response
:type response: requests.Response
@@ -354,7 +358,8 @@ def get_auth_scheme(response):
auths = response.headers['www-authenticate'].split(',')
scheme = next((auth_type.rstrip().lower() for auth_type in auths
if auth_type.rstrip().lower().startswith("basic") or
auth_type.rstrip().lower().startswith("acsjwt")),
auth_type.rstrip().lower().startswith("acsjwt") or
auth_type.rstrip().lower().startswith("oauthjwt")),
None)
if scheme:
scheme_info = scheme.split("=")
@@ -385,7 +390,7 @@ def _get_http_auth(response, url, auth_scheme):
password = url.password
if 'www-authenticate' in response.headers:
if auth_scheme not in ['basic', 'acsjwt']:
if auth_scheme not in ['basic', 'acsjwt', 'oauthjwt']:
msg = ("Server responded with an HTTP 'www-authenticate' field of "
"'{}', DCOS only supports 'Basic'".format(
response.headers['www-authenticate']))
@@ -396,17 +401,59 @@ def _get_http_auth(response, url, auth_scheme):
# we'd already be authed by python requests module
username, password = _get_auth_credentials(username, hostname)
return HTTPBasicAuth(username, password)
# dcos auth (acs or oauth)
else:
return _get_dcos_acs_auth(username, password, hostname)
return _get_dcos_auth(auth_scheme, username, password, hostname)
else:
msg = ("Invalid HTTP response: server returned an HTTP 401 response "
"with no 'www-authenticate' field")
raise DCOSException(msg)
def _get_dcos_acs_auth(username, password, hostname):
"""Get authentication flow for dcos acs auth
def _get_dcos_oauth_creds(dcos_url):
"""Get token credential for dcos oath
:param dcos_url: dcos cluster url
:type dcos_url: str
:returns: token from browser for oauth flow
:rtype: dict
"""
oauth_login = 'login?redirect_uri=urn:ietf:wg:oauth:2.0:oob'
url = urllib.parse.urljoin(dcos_url, oauth_login)
msg = "\n{}\n\n {}\n\n{} ".format(
"Please go to the following link in your browser:",
url,
"Enter authentication token:")
sys.stderr.write(msg)
sys.stderr.flush()
token = sys.stdin.readline().strip()
return {"token": token}
def _get_dcos_acs_auth_creds(username, password, hostname):
"""Get credentials for dcos acs auth
:param username: username user for authentication
:type username: str
:param password: password for authentication
:type password: str
:param hostname: hostname for credentials
:type hostname: str
:returns: username/password credentials
:rtype: dict
"""
if password is None:
username, password = _get_auth_credentials(username, hostname)
return {"uid": username, "password": password}
def _get_dcos_auth(auth_scheme, username, password, hostname):
"""Get authentication flow for dcos acs auth and dcos oauth
:param auth_scheme: authentication_scheme
:type auth_scheme: str
:param username: username user for authentication
:type username: str
:param password: password for authentication
@@ -421,16 +468,17 @@ def _get_dcos_acs_auth(username, password, hostname):
token = toml_config.get("core.dcos_acs_token")
if token is None:
dcos_url = toml_config.get("core.dcos_url")
url = urllib.parse.urljoin(dcos_url, 'acs/api/v1/auth/login')
if password is None:
username, password = _get_auth_credentials(username, hostname)
creds = {"uid": username, "password": password}
if auth_scheme == "acsjwt":
creds = _get_dcos_acs_auth_creds(username, password, hostname)
else:
creds = _get_dcos_oauth_creds(dcos_url)
verify = _verify_ssl()
# Silence 'Unverified HTTPS request' and 'SecurityWarning' for bad cert
if verify is not None:
silence_requests_warnings()
url = urllib.parse.urljoin(dcos_url, 'acs/api/v1/auth/login')
# using private method here, so we don't retry on this request
# error here will be bubbled up to _request_with_auth
response = _request('post', url, json=creds, verify=verify)

View File

@@ -134,8 +134,8 @@ def default_subcommands():
:returns: list of all the default dcos cli subcommands
:rtype: [str]
"""
return ["config", "help", "marathon", "node", "package", "service", "task"]
return ["auth", "config", "help", "marathon",
"node", "package", "service", "task"]
def documentation(executable_path):