Basic Example, does nothing

This commit is contained in:
Christiaan Goossens
2022-11-28 12:49:41 +01:00
commit 74b88a2252
9 changed files with 1923 additions and 0 deletions

113
.gitignore vendored Normal file
View File

@@ -0,0 +1,113 @@
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# End of https://www.gitignore.io/api/python
config/
.venv

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright 2022 Christiaan Goossens
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
# OIDC Auth for Home Assistant
TODO
## Installation
Add this repository to [HACS](https://hacs.xyz/).
Update your configuration.yaml file with
```yaml
```
Afterwards, restart Home Assistant.
## Development
This package uses poetry: https://github.com/python-poetry/poetry. Use `poetry install` to install.
You can force the venv within the project with `poetry config virtualenvs.in-project true`.

View File

@@ -0,0 +1,31 @@
import logging
from typing import OrderedDict
import voluptuous as vol
from homeassistant.core import HomeAssistant
from .example import ExampleAuthProvider
DOMAIN = "auth_oidc"
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
{
},
extra=vol.ALLOW_EXTRA,
)
async def async_setup(hass: HomeAssistant, config):
"""TODO"""
# Inject Auth-Header provider.
providers = OrderedDict()
provider = ExampleAuthProvider(
hass,
hass.auth._store,
config[DOMAIN],
)
providers[(provider.type, provider.id)] = provider
providers.update(hass.auth._providers)
hass.auth._providers = providers
_LOGGER.debug("Injected example provider")
return True

View File

@@ -0,0 +1,112 @@
"""OIDC Provider"""
from __future__ import annotations
from collections.abc import Mapping
import hmac
from typing import Any, cast
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.auth.providers import (
AUTH_PROVIDERS,
AuthProvider,
LoginFlow,
)
from homeassistant.auth.models import Credentials, UserMeta
class InvalidAuthError(HomeAssistantError):
"""Raised when submitting invalid authentication."""
@AUTH_PROVIDERS.register("insecure_example_2")
class ExampleAuthProvider(AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""
DEFAULT_TITLE = "OpenID Connect (SSO)"
@property
def type(self) -> str:
return "auth_oidc"
@property
def support_mfa(self) -> bool:
"""OIDC Authentication Provider does not support MFA in Home Assistant, only external."""
return False
async def async_login_flow(self) -> LoginFlow:
"""Return a flow to login."""
return ExampleLoginFlow(self)
@callback
def async_validate_login(self, input: str) -> None:
"""Validate a username and password."""
if input is "example":
return
else:
raise InvalidAuthError
async def async_get_or_create_credentials(
self, flow_result: Mapping[str, str]
) -> Credentials:
"""Get credentials based on the flow result."""
username = flow_result["input"]
for credential in await self.async_credentials():
if credential.data["input"] == username:
return credential
# Create new credentials.
return self.async_create_credentials({"username": username})
async def async_user_meta_for_credentials(
self, credentials: Credentials
) -> UserMeta:
"""Return extra user metadata for credentials.
Will be used to populate info when creating a new user.
"""
username = credentials.data["username"]
name = None
for user in self.config["users"]:
if user["username"] == username:
name = user.get("name")
break
return UserMeta(name=name, is_active=True)
class ExampleLoginFlow(LoginFlow):
"""Handler for the login flow."""
async def async_step_init(
self, user_input: dict[str] | None = None
) -> FlowResult:
"""Handle the step of the form."""
errors = None
if user_input is not None:
try:
cast(ExampleAuthProvider, self._auth_provider).async_validate_login(
user_input["input"]
)
except InvalidAuthError:
errors = {"base": "invalid_auth"}
if not errors:
return await self.async_finish(user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required("input"): str,
}
),
errors=errors,
)

View File

@@ -0,0 +1,14 @@
{
"domain": "auth_oidc",
"name": "OIDC Authentication",
"documentation": "",
"requirements": [],
"ssdp": [],
"zeroconf": [],
"homekit": {},
"dependencies": [
"auth"
],
"codeowners": ["@christiaangoossens"],
"version": "0.1"
}

5
hacs.json Normal file
View File

@@ -0,0 +1,5 @@
{
"name": "OpenID Connect",
"render_readme": true,
"homeassistant": "2022.11"
}

1609
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

13
pyproject.toml Normal file
View File

@@ -0,0 +1,13 @@
[tool.poetry]
name = "hass-oidc"
version = "0.1.0"
description = ""
authors = ["Christiaan Goossens <contact@christiaangoossens.nl>"]
license = "MIT"
[tool.poetry.dependencies]
python = "3.10.*"
[tool.poetry.dev-dependencies]
homeassistant = "^2022.11.4"
pylint = "^2.15.6"