Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
László Vaskó committed Sep 11, 2019
2 parents cc87d69 + 8a9d9af commit f6fe7db
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 16 deletions.
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
# openconnect-sso
Wrapper script for openconnect supporting Azure AD (SAMLv2) authentication
Wrapper script for OpenConnect supporting Azure AD (SAMLv2) authentication

## TL; DR
```bash
$ pip install openconnect-sso-$VERSION.whl
$ openconnect-sso --server vpn.server.com/group
```

## Configuration
If you want to save credentials and get them automatically
injected in the web browser:
```bash
$ openconnect-sso --server vpn.server.com/group --user [email protected]
Password ([email protected]):
[info ] Authenticating to VPN endpoint ...
```

User credentials are automatically saved to the users login keyring (if available).

If you already have Cisco Anyconnect set-up, then `--server` argument is optional.
Also, the last used `--server` address is saved between sessions so there is no need
to always type in the same arguments:

```bash
$ openconnect-sso
[info ] Authenticating to VPN endpoint ...
```

Configuration is saved in `$XDG_CONFIG_HOME/openconnect-sso/config.toml`. On typical
Linux installations it is located under `$HOME/.configopenconnect-sso/config.toml`
4 changes: 2 additions & 2 deletions openconnect_sso/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.1.0"
__version__ = "0.2.0"
__description__ = (
"Wrapper script for openconnect supporting Azure AD (SAMLv2) authentication"
"Wrapper script for OpenConnect supporting Azure AD (SAMLv2) authentication"
)
11 changes: 9 additions & 2 deletions openconnect_sso/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async def _run(args):

if cfg.default_profile and not args.use_profile_selector:
selected_profile = cfg.default_profile
else:
elif args.use_profile_selector:
profiles = get_profiles(Path(args.profile_path))
if not profiles:
logger.error("No profile found")
Expand All @@ -73,7 +73,14 @@ async def _run(args):
if not selected_profile:
logger.error("No profile selected")
return 18
cfg.default_profile = selected_profile
elif args.server:
selected_profile = config.HostProfile(args.server, args.usergroup)
else:
raise ValueError(
"Cannot determine server address. Invalid arguments specified."
)

cfg.default_profile = selected_profile

config.save(cfg)

Expand Down
43 changes: 38 additions & 5 deletions openconnect_sso/cli.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import argparse
import enum
import logging
import os

import openconnect_sso
from openconnect_sso import app
from openconnect_sso import app, config


def create_argparser():
parser = argparse.ArgumentParser(
prog="openconnect-sso", description=openconnect_sso.__description__
)
parser.add_argument(

server_settings = parser.add_argument_group("Server connection")
server_settings.add_argument(
"-p",
"--profile",
dest="profile_path",
help="Use a profile from this file or directory",
default="/opt/cisco/anyconnect/profile",
)

parser.add_argument(
server_settings.add_argument(
"-P",
"--profile-selector",
dest="use_profile_selector",
Expand All @@ -27,6 +29,20 @@ def create_argparser():
default=False,
)

server_settings.add_argument(
"-s",
"--server",
help="VPN server to connect to. The following forms are accepted: "
"vpn.server.com, vpn.server.com/usergroup, "
"https://vpn.server.com, https.vpn.server.com.usergroup",
)
server_settings.add_argument(
"-g",
"--usergroup",
help="Override usergroup setting from --server argument",
default="",
)

parser.add_argument(
"--login-only",
help="Complete authentication but do not acquire a session token or initiate a connection",
Expand Down Expand Up @@ -57,8 +73,9 @@ def create_argparser():


class LogLevel(enum.IntEnum):
INFO = logging.INFO
ERROR = logging.ERROR
WARNING = logging.WARNING
INFO = logging.INFO
DEBUG = logging.DEBUG

def __str__(self):
Expand All @@ -76,4 +93,20 @@ def choices(cls):
def main():
parser = create_argparser()
args = parser.parse_args()

if (args.profile_path or args.use_profile_selector) and (
args.server or args.usergroup
):
parser.error(
"--profile/--profile-selector and --server/--usergroup are mutually exclusive"
)

if not args.profile_path and not args.server and not config.load().default_profile:
if os.path.exists("/opt/cisco/anyconnect/profiles"):
args.profile_path = "/opt/cisco/anyconnect/profiles"
else:
parser.error(
"No Anyconnect profile can be found. One of --profile or --server arguments required."
)

return app.run(args)
12 changes: 9 additions & 3 deletions openconnect_sso/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from urllib.parse import urlunparse
from urllib.parse import urlparse, urlunparse

import attr
import keyring
Expand Down Expand Up @@ -59,13 +59,19 @@ def as_dict(self):

@attr.s
class HostProfile(ConfigNode):
name = attr.ib(converter=str)
address = attr.ib(converter=str)
user_group = attr.ib(converter=str)
name = attr.ib(converter=str, default="UNNAMED")

@property
def vpn_url(self):
return urlunparse(("https", self.address, self.user_group, "", "", ""))
parts = urlparse(self.address)
group = self.user_group or parts.path
if parts.path == self.address and not self.user_group:
group = ""
return urlunparse(
(parts.scheme or "https", parts.netloc or self.address, group, "", "", "")
)


@attr.s
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]
name = "openconnect-sso"
version = "0.1.0"
description = "Wrapper script for openconnect supporting Azure AD (SAMLv2) authentication"
version = "0.2.0"
description = "Wrapper script for OpenConnect supporting Azure AD (SAMLv2) authentication"
authors = ["László Vaskó <[email protected]>"]
readme = "README.md"

Expand Down
17 changes: 17 additions & 0 deletions tests/test_hostprofile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest

from openconnect_sso.config import HostProfile


@pytest.mark.parametrize(("server", "group", "expected_url"), (
("hostname", "", "https://hostname"),
("hostname", "group", "https://hostname/group"),
("hostname/group", "", "https://hostname/group"),
("https://hostname", "group", "https://hostname/group"),
("https://server.com", "group", "https://server.com/group"),
("https://hostname/group", "", "https://hostname/group"),
("https://hostname:8443/group", "", "https://hostname:8443/group"),
))
def test_vpn_url(server, group, expected_url):
assert HostProfile(server, group).vpn_url == expected_url

2 changes: 1 addition & 1 deletion tests/test_openconnect_sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@


def test_version():
assert __version__ == '0.1.0'
assert __version__ == '0.2.0'

0 comments on commit f6fe7db

Please sign in to comment.