Skip to content
Snippets Groups Projects
Verified Commit 408ee0a3 authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Implement PAM auth step

parent 21abd44d
No related branches found
No related tags found
No related merge requests found
...@@ -18,13 +18,15 @@ import toml ...@@ -18,13 +18,15 @@ import toml
DEFAULT_CONFIG_FILE = "/etc/nss_pam_oidc.toml" DEFAULT_CONFIG_FILE = "/etc/nss_pam_oidc.toml"
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"pam": {}, "pam": {
"flow": "password",
},
"nss": {}, "nss": {},
} }
def get_config(section: str, **kwargs): def load_config(config_file: str) -> dict[str, dict[str, str]]:
"""Get configuration for a section, layered from defaults, config file, and args.""" """Load full configuration, amended wit hdefault config."""
config = {} config = {}
# First, copy default configuration # First, copy default configuration
...@@ -32,13 +34,25 @@ def get_config(section: str, **kwargs): ...@@ -32,13 +34,25 @@ def get_config(section: str, **kwargs):
config[name] = conf.copy() config[name] = conf.copy()
# Second, load configuration from file # Second, load configuration from file
config_file = kwargs.pop("config", DEFAULT_CONFIG_FILE)
if os.path.exists(config_file): if os.path.exists(config_file):
config_toml = toml.load(config_file) config_toml = toml.load(config_file)
for name, conf in config.items(): for name, conf in config.items():
conf.update(config_toml.get(name, {})) conf.update(config_toml.get(name, {}))
return config
def get_config(section: str, **kwargs) -> dict[str, str]:
"""Get configuration for a section, layered from defaults, config file, and args."""
config_file = kwargs.pop("config", DEFAULT_CONFIG_FILE)
config = load_config(config_file)
# Lastly, override with passed arguments # Lastly, override with passed arguments
config[section].update(kwargs) config[section].update(kwargs)
return config return config[section]
def filter_config(config: dict[str, str], keys: list[str]) -> dict[str, str]:
"""Return a copy of the config dictionary with only the requested keys."""
return {k: v for k, v in config.items() if k in keys}
...@@ -12,26 +12,79 @@ ...@@ -12,26 +12,79 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from oauthlib.oauth2 import InvalidGrantError, LegacyApplicationClient
from requests.exceptions import RequestException
from requests_oauthlib import OAuth2Session
from .config import filter_config, get_config
def _split_argv(argv: list[str]) -> dict[str, str]:
args = {}
for arg in argv:
if "=" in arg:
name, val = arg.split("=", 1)
else:
name, val = arg, True
args[name] = val
return args
def _do_legacy_auth(username: str, password: str, config: dict[str, str]):
client_config = filter_config(config, ["client_id"])
session_config = filter_config(config, ["client_id"])
fetch_config = filter_config(config, ["client_id", "client_secret", "token_url"])
client = LegacyApplicationClient(**client_config)
session = OAuth2Session(client=client, **session_config)
token = session.fetch_token(username=username, password=password, **fetch_config)
return token
def pam_sm_authenticate(pamh, flags, argv): def pam_sm_authenticate(pamh, flags, argv):
try: args = _split_argv(argv)
user = pamh.get_user(None) config = get_config("pam", args)
except pamh.exception as e:
return e.pam_result if config.pop("flow") == "password":
if user == None: try:
pamh.user = DEFAULT_USER user = pamh.get_user(None)
return pamh.PAM_SUCCESS password = pamh.authtok
except pamh.exception as e:
return e.pam_result
if user is None or password is None:
return pamh.PAM_CRED_INSUFFICIENT
try:
token = _do_legacy_auth(username, password)
except InvalidGrantError:
return pamh.PAM_AUTH_ERROR
except RequestException:
return pamh.PAM_AUTHINFO_UNAVAIL
except:
return pamh.PAM_SERVICE_ERR
if "access_token" in token:
return pamh.PAM_SUCCESS
return pamh.PAM_AUTH_ERR
def pam_sm_setcred(pamh, flags, argv): def pam_sm_setcred(pamh, flags, argv):
return pamh.PAM_SUCCESS return pamh.PAM_SUCCESS
def pam_sm_acct_mgmt(pamh, flags, argv): def pam_sm_acct_mgmt(pamh, flags, argv):
return pamh.PAM_SUCCESS return pamh.PAM_SUCCESS
def pam_sm_open_session(pamh, flags, argv): def pam_sm_open_session(pamh, flags, argv):
return pamh.PAM_SUCCESS return pamh.PAM_SUCCESS
def pam_sm_close_session(pamh, flags, argv): def pam_sm_close_session(pamh, flags, argv):
return pamh.PAM_SUCCESS return pamh.PAM_SUCCESS
def pam_sm_chauthtok(pamh, flags, argv): def pam_sm_chauthtok(pamh, flags, argv):
return pamh.PAM_SUCCESS return pamh.PAM_SUCCESS
...@@ -14,6 +14,8 @@ classifiers = [ ...@@ -14,6 +14,8 @@ classifiers = [
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9" python = "^3.9"
toml = "^0.10.2" toml = "^0.10.2"
oauthlib = "^3.1.0"
requests-oauthlib = "^1.3.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment