def main_add_basic(args): """ Add a config entry for basic auth """ backend = args.backend username = args.username try_auth = args.try_auth config = AuthConfig() print("Will add basic auth config for backend URL {b!r}".format(b=backend)) print("to config file: {c!r}".format(c=str(config.path))) # Find username and password if not username: username = builtins.input("Enter username and press enter: ") print("Using username {u!r}".format(u=username)) password = getpass("Enter password and press enter: ") or None if try_auth: print("Trying to authenticate with {b!r}".format(b=backend)) con = connect(backend) con.authenticate_basic(username, password) print("Successfully authenticated {u!r}".format(u=username)) config.set_basic_auth(backend=backend, username=username, password=password) print("Saved credentials to {p!r}".format(p=str(config.path)))
def test_tmp_openeo_config_home(self, tmp_openeo_config_home, tmp_path): expected_dir = str(tmp_path) assert str(AuthConfig.default_path()).startswith(expected_dir) assert not AuthConfig.default_path().exists() config = AuthConfig() assert str(config.path).startswith(expected_dir) assert config.load() == {}
def main_config_dump(args): """ Dump auth config file """ config = AuthConfig() print("### {p} ".format(p=str(config.path)).ljust(80, "#")) data = config.load(empty_on_file_not_found=False) if not args.show_secrets: _redact(data, keys_to_redact=["client_secret", "password", "refresh_token"]) json.dump(data, fp=sys.stdout, indent=2) print()
def main_paths(args): """ Print paths of auth config file and refresh token cache file. """ def describe(p: Path): if p.exists(): return "perms: 0o{p:o}, size: {s}B".format(p=p.stat().st_mode & 0o777, s=p.stat().st_size) else: return "does not exist" config_path = AuthConfig().path print("openEO auth config: {p} ({d})".format(p=str(config_path), d=describe(config_path))) tokens_path = RefreshTokenStore().path print("openEO OpenID Connect refresh token store: {p} ({d})".format( p=str(tokens_path), d=describe(tokens_path)))
def test_authenticate_oidc_resource_owner_password_credentials_client_from_config( requests_mock): requests_mock.get(API_URL, json={"api_version": "1.0.0"}) client_id = "myclient" client_secret = "$3cr3t" username, password = "******", "j0hn" issuer = "https://oidc.test" oidc_discovery_url = "https://oidc.test/.well-known/openid-configuration" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [{ "id": "oi", "issuer": issuer, "title": "example", "scopes": ["openid"] }] }) oidc_mock = OidcMock( requests_mock=requests_mock, expected_grant_type="password", expected_client_id=client_id, expected_fields={ "username": username, "password": password, "scope": "openid", "client_secret": client_secret }, oidc_discovery_url=oidc_discovery_url, ) AuthConfig().set_oidc_client_config(backend=API_URL, provider_id="oi", client_id=client_id, client_secret=client_secret) # With all this set up, kick off the openid connect flow refresh_token_store = mock.Mock() conn = Connection(API_URL, refresh_token_store=refresh_token_store) assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_resource_owner_password_credentials( username=username, password=password) assert isinstance(conn.auth, BearerAuth) assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == []
def test_oidc_backend_normalization(self, tmp_path, to_set, to_get): config = AuthConfig(path=tmp_path) with mock.patch.object(openeo.rest.auth.config, "utcnow_rfc3339", return_value="2020-06-08T11:18:27Z"): config.set_oidc_client_config(to_set, "default", client_id="client123", client_secret="$6cr67") for backend in [to_set, to_get]: assert config.get_oidc_client_configs(backend, "default") == ("client123", "$6cr67") assert config.get_oidc_provider_configs(backend) == { "default": { "date": "2020-06-08T11:18:27Z", "client_id": "client123", "client_secret": "$6cr67" } }
def test_authenticate_basic_from_config(requests_mock, api_version): user, pwd = "john281", "J0hndo3" requests_mock.get(API_URL, json={"api_version": api_version}) def text_callback(request, context): assert request.headers[ "Authorization"] == requests.auth._basic_auth_str(username=user, password=pwd) return '{"access_token":"w3lc0m3"}' requests_mock.get(API_URL + 'credentials/basic', text=text_callback) AuthConfig().set_basic_auth(backend=API_URL, username=user, password=pwd) conn = Connection(API_URL) assert isinstance(conn.auth, NullAuth) conn.authenticate_basic() assert isinstance(conn.auth, BearerAuth) if ComparableVersion(api_version).at_least("1.0.0"): assert conn.auth.bearer == "basic//w3lc0m3" else: assert conn.auth.bearer == "w3lc0m3"
def test_basic_auth(self, tmp_path): config = AuthConfig(path=tmp_path) with mock.patch.object(openeo.rest.auth.config, "utcnow_rfc3339", return_value="2020-06-08T11:18:27Z"): config.set_basic_auth("oeo.test", "John", "j0hn123") assert config.path.exists() assert [p.name for p in tmp_path.iterdir()] == [AuthConfig.DEFAULT_FILENAME] with config.path.open("r") as f: data = json.load(f) assert data["backends"] == { "oeo.test": { "basic": { "date": "2020-06-08T11:18:27Z", "username": "******", "password": "******" } } } assert config.get_basic_auth("oeo.test") == ("John", "j0hn123") assert config.get_basic_auth("oeo.test") == ("John", "j0hn123")
def test_authenticate_oidc_device_flow_client_from_config(requests_mock): requests_mock.get(API_URL, json={"api_version": "1.0.0"}) client_id = "myclient" client_secret = "$3cr3t" issuer = "https://oidc.test" oidc_discovery_url = "https://oidc.test/.well-known/openid-configuration" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [{ "id": "oi", "issuer": issuer, "title": "example", "scopes": ["openid"] }] }) oidc_mock = OidcMock( requests_mock=requests_mock, expected_grant_type="urn:ietf:params:oauth:grant-type:device_code", expected_client_id=client_id, expected_fields={ "scope": "openid", "client_secret": client_secret }, oidc_discovery_url=oidc_discovery_url, ) AuthConfig().set_oidc_client_config(backend=API_URL, provider_id="oi", client_id=client_id, client_secret=client_secret) # With all this set up, kick off the openid connect flow refresh_token_store = mock.Mock() conn = Connection(API_URL, refresh_token_store=refresh_token_store) assert isinstance(conn.auth, NullAuth) oidc_mock.state["device_code_callback_timeline"] = ["great success"] conn.authenticate_oidc_device() assert isinstance(conn.auth, BearerAuth) assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == []
def test_oidc(self, tmp_path): config = AuthConfig(path=tmp_path) with mock.patch.object(openeo.rest.auth.config, "utcnow_rfc3339", return_value="2020-06-08T11:18:27Z"): config.set_oidc_client_config("oeo.test", "default", client_id="client123", client_secret="$6cr67") assert config.path.exists() assert [p.name for p in tmp_path.iterdir()] == [AuthConfig.DEFAULT_FILENAME] with config.path.open("r") as f: data = json.load(f) assert data["backends"] == { "oeo.test": { "oidc": { "providers": { "default": { "date": "2020-06-08T11:18:27Z", "client_id": "client123", "client_secret": "$6cr67" } } } } } assert config.get_oidc_client_configs("oeo.test", "default") == ("client123", "$6cr67") assert config.get_oidc_provider_configs("oeo.test") == { "default": { "date": "2020-06-08T11:18:27Z", "client_id": "client123", "client_secret": "$6cr67" } }
def test_authenticate_oidc_auth_code_pkce_flow_client_from_config( requests_mock): requests_mock.get(API_URL, json={"api_version": "1.0.0"}) client_id = "myclient" issuer = "https://oidc.test" oidc_discovery_url = "https://oidc.test/.well-known/openid-configuration" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [{ "id": "oi", "issuer": issuer, "title": "example", "scopes": ["openid"] }] }) oidc_mock = OidcMock( requests_mock=requests_mock, expected_grant_type="authorization_code", expected_client_id=client_id, expected_fields={"scope": "openid"}, oidc_discovery_url=oidc_discovery_url, scopes_supported=["openid"], ) AuthConfig().set_oidc_client_config(backend=API_URL, provider_id="oi", client_id=client_id) # With all this set up, kick off the openid connect flow refresh_token_store = mock.Mock() conn = Connection(API_URL, refresh_token_store=refresh_token_store) assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_authorization_code( webbrowser_open=oidc_mock.webbrowser_open) assert isinstance(conn.auth, BearerAuth) assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == []
def main_add_oidc(args): """ Add a config entry for OIDC auth """ backend = args.backend provider_id = args.provider_id client_id = args.client_id config = AuthConfig() print("Will add OpenID Connect auth config for backend URL {b!r}".format( b=backend)) print("to config file: {c!r}".format(c=str(config.path))) con = connect(backend) api_version = con.capabilities().api_version_check if api_version < "1.0.0": raise CliToolException( "Backend API version is too low: {v} < 1.0.0".format( v=api_version)) # Find provider ID oidc_info = con.get("/credentials/oidc", expected_status=200).json() providers = OrderedDict([(p["id"], p) for p in oidc_info["providers"]]) if not providers: raise CliToolException( "No OpenID Connect providers listed by backend {b!r}.".format( b=backend)) if not provider_id: if len(providers) == 1: provider_id = list(providers.keys())[0] else: provider_id = _interactive_choice( title="Backend {b!r} has multiple OpenID Connect providers.". format(b=backend), options=[(p["id"], "{t} (issuer {s})".format(t=p["title"], s=p["issuer"])) for p in providers.values()]) if provider_id not in providers: raise CliToolException( "Invalid provider ID {p!r}. Should be one of {o}.".format( p=provider_id, o=list(providers.keys()))) issuer = providers[provider_id]["issuer"] print("Using provider ID {p!r} (issuer {i!r})".format(p=provider_id, i=issuer)) # Get client_id and client_secret # Find username and password if not client_id: client_id = builtins.input("Enter client_id and press enter: ") print("Using client ID {u!r}".format(u=client_id)) if not client_id: show_warning("Given client ID was empty.") client_secret = getpass("Enter client_secret and press enter: ") if not client_secret: show_warning("Given client secret was empty.") config.set_oidc_client_config(backend=backend, provider_id=provider_id, client_id=client_id, client_secret=client_secret, issuer=issuer) print("Saved client information to {p!r}".format(p=str(config.path)))
def main_add_oidc(args): """ Add a config entry for OIDC auth """ backend = args.backend provider_id = args.provider_id client_id = args.client_id ask_client_secret = args.ask_client_secret use_default_client = args.use_default_client config = AuthConfig() print("Will add OpenID Connect auth config for backend URL {b!r}".format( b=backend)) print("to config file: {c!r}".format(c=str(config.path))) con = connect(backend) api_version = con.capabilities().api_version_check if api_version < "1.0.0": raise CliToolException( "Backend API version is too low: {v} < 1.0.0".format( v=api_version)) # Find provider ID oidc_info = con.get("/credentials/oidc", expected_status=200).json() providers = OrderedDict((p["id"], OidcProviderInfo.from_dict(p)) for p in oidc_info["providers"]) if not providers: raise CliToolException( "No OpenID Connect providers listed by backend {b!r}.".format( b=backend)) if not provider_id: if len(providers) == 1: provider_id = list(providers.keys())[0] else: provider_id = _interactive_choice( title="Backend {b!r} has multiple OpenID Connect providers.". format(b=backend), options=[(p.id, "{t} (issuer {s})".format(t=p.title, s=p.issuer)) for p in providers.values()]) if provider_id not in providers: raise CliToolException( "Invalid provider ID {p!r}. Should be one of {o}.".format( p=provider_id, o=list(providers.keys()))) provider = providers[provider_id] print("Using provider ID {p!r} (issuer {i!r})".format(p=provider_id, i=provider.issuer)) # Get client_id and client_secret (if necessary) if use_default_client: if not provider.default_clients: show_warning( "No default clients declared for provider {p!r}".format( p=provider_id)) client_id, client_secret = None, None else: if not client_id: if provider.default_clients: client_prompt = "Enter client_id or leave empty to use default client, and press enter: " else: client_prompt = "Enter client_id and press enter: " client_id = builtins.input(client_prompt).strip() or None print("Using client ID {u!r}".format(u=client_id)) if not client_id and not provider.default_clients: show_warning("Given client ID was empty.") if client_id and ask_client_secret: client_secret = getpass( "Enter client_secret or leave empty to not use a secret, and press enter: " ) or None else: client_secret = None config.set_oidc_client_config(backend=backend, provider_id=provider_id, client_id=client_id, client_secret=client_secret, issuer=provider.issuer) print("Saved client information to {p!r}".format(p=str(config.path)))
def main_oidc_auth(args): """ Do OIDC auth flow and store refresh tokens. """ backend = args.backend oidc_flow = args.flow provider_id = args.provider_id timeout = args.timeout config = AuthConfig() print("Will do OpenID Connect flow to authenticate with backend {b!r}.". format(b=backend)) print("Using config {c!r}.".format(c=str(config.path))) # Determine provider provider_configs = config.get_oidc_provider_configs(backend=backend) _log.debug("Provider configs: {c!r}".format(c=provider_configs)) if not provider_id: if len(provider_configs) == 0: print("Will try to use default provider_id.") provider_id = None elif len(provider_configs) == 1: provider_id = list(provider_configs.keys())[0] else: provider_id = _interactive_choice( title= "Multiple OpenID Connect providers available for backend {b!r}" .format(b=backend), options=sorted( (k, "{k}: issuer {s}".format(k=k, s=v.get("issuer", "n/a"))) for k, v in provider_configs.items())) if not (provider_id is None or provider_id in provider_configs): raise CliToolException( "Invalid provider ID {p!r}. Should be `None` or one of {o}.". format(p=provider_id, o=list(provider_configs.keys()))) print("Using provider ID {p!r}.".format(p=provider_id)) # Get client id and secret client_id, client_secret = config.get_oidc_client_configs( backend=backend, provider_id=provider_id) if client_id: print("Using client ID {c!r}.".format(c=client_id)) else: print("Will try to use default client.") refresh_token_store = RefreshTokenStore() con = Connection(backend, refresh_token_store=refresh_token_store) if oidc_flow == "auth-code": print("Starting OpenID Connect authorization code flow:") print( "a browser window should open allowing you to log in with the identity provider\n" "and grant access to the client {c!r} (timeout: {t}s).".format( c=client_id, t=timeout)) con.authenticate_oidc_authorization_code( client_id=client_id, client_secret=client_secret, provider_id=provider_id, timeout=timeout, store_refresh_token=True, webbrowser_open=_webbrowser_open) print("The OpenID Connect authorization code flow was successful.") elif oidc_flow == "device": print("Starting OpenID Connect device flow.") con.authenticate_oidc_device(client_id=client_id, client_secret=client_secret, provider_id=provider_id, store_refresh_token=True) print("The OpenID Connect device flow was successful.") else: raise CliToolException("Invalid flow {f!r}".format(f=oidc_flow)) print("Stored refresh token in {p!r}".format( p=str(refresh_token_store.path)))
def _get_auth_config(self) -> AuthConfig: if self._auth_config is None: self._auth_config = AuthConfig() return self._auth_config
def auth_config(tmp_openeo_config) -> AuthConfig: return AuthConfig(tmp_openeo_config)
def test_start_empty(self, tmp_path): config = AuthConfig(path=tmp_path) assert config.get_basic_auth("foo") == (None, None) assert config.get_oidc_client_configs("oeo.test", "default") == (None, None)
def test_basic_auth_url_normalization(self, tmp_path, to_set, to_get): config = AuthConfig(path=tmp_path) config.set_basic_auth(to_set, "John", "j0hn123") assert config.get_basic_auth(to_set) == ("John", "j0hn123") assert config.get_basic_auth(to_get) == ("John", "j0hn123")
def auth_config(tmp_openeo_config_home) -> AuthConfig: """Make sure we start with emtpy AuthConfig.""" config = AuthConfig(tmp_openeo_config_home) assert not config.path.exists() return config