mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-08-31 03:40:21 +00:00
Compare commits
87 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ab83c51910 | ||
![]() |
2ae2d0e107 | ||
![]() |
613ef9010a | ||
![]() |
675bea7835 | ||
![]() |
3d74e07c5e | ||
![]() |
692d34a13c | ||
![]() |
440379680e | ||
![]() |
165af46f54 | ||
![]() |
4c2d729646 | ||
![]() |
8ffd227849 | ||
![]() |
64c5ba1635 | ||
![]() |
37a247160e | ||
![]() |
919f1e9149 | ||
![]() |
d73d8d00f0 | ||
![]() |
09c699a2fe | ||
![]() |
cb992762d1 | ||
![]() |
8f0cec10d5 | ||
![]() |
4a0e17f050 | ||
![]() |
b4c74404e3 | ||
![]() |
649091f3bd | ||
![]() |
a27be5d621 | ||
![]() |
939eb81581 | ||
![]() |
ee1daa0b35 | ||
![]() |
242c05a19b | ||
![]() |
9024085712 | ||
![]() |
e0abb98aaf | ||
![]() |
4ffa628a6e | ||
![]() |
417ee418f2 | ||
![]() |
0f79ba5a3d | ||
![]() |
47fd849319 | ||
![]() |
99e0eab958 | ||
![]() |
0a753c55ca | ||
![]() |
72d81e43dd | ||
![]() |
83e5359bd2 | ||
![]() |
51875bdcd5 | ||
![]() |
ecabf9dea7 | ||
![]() |
c1954f4426 | ||
![]() |
0991f52100 | ||
![]() |
fed4a05003 | ||
![]() |
089635f4d3 | ||
![]() |
15fa8de05c | ||
![]() |
8fc91f5288 | ||
![]() |
4461192fa7 | ||
![]() |
2fe7c0dce6 | ||
![]() |
e2e11faf18 | ||
![]() |
fcbef6b78b | ||
![]() |
10810fb1b9 | ||
![]() |
92408bb893 | ||
![]() |
61fc01915f | ||
![]() |
fea60c57a2 | ||
![]() |
c1ac6c0432 | ||
![]() |
64ca530e66 | ||
![]() |
08f290ca10 | ||
![]() |
03849258eb | ||
![]() |
32d0d84c53 | ||
![]() |
83265c4dc5 | ||
![]() |
a9cbeb21c9 | ||
![]() |
1af4a362c2 | ||
![]() |
b9e2cfad4d | ||
![]() |
726ded70d3 | ||
![]() |
ac56f1511f | ||
![]() |
3d7d52a62b | ||
![]() |
941e1f5c91 | ||
![]() |
1a2b13018a | ||
![]() |
da721f455e | ||
![]() |
4e91db10a9 | ||
![]() |
ba9bcd9e57 | ||
![]() |
c193c91fe7 | ||
![]() |
bdde24ae9e | ||
![]() |
b56995be27 | ||
![]() |
1f7199cf00 | ||
![]() |
e48e024bb3 | ||
![]() |
02c181c1ff | ||
![]() |
70cf6cc0d9 | ||
![]() |
9abf38f285 | ||
![]() |
54dfba1faa | ||
![]() |
ed778f09ee | ||
![]() |
b044095e57 | ||
![]() |
c41f13bf18 | ||
![]() |
2ddb5ca53f | ||
![]() |
fad75810ab | ||
![]() |
4d9e30adef | ||
![]() |
80a6171692 | ||
![]() |
815669e6e3 | ||
![]() |
a8133f0640 | ||
![]() |
2809f23391 | ||
![]() |
348fb56cb5 |
16
.github/workflows/builder.yml
vendored
16
.github/workflows/builder.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
requirements: ${{ steps.requirements.outputs.changed }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
|
||||
|
||||
- name: Build supervisor
|
||||
uses: home-assistant/builder@2022.09.0
|
||||
uses: home-assistant/builder@2022.11.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -156,13 +156,13 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
- name: Initialize git
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
@@ -220,11 +220,11 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
|
||||
- name: Build the Supervisor
|
||||
if: needs.init.outputs.publish != 'true'
|
||||
uses: home-assistant/builder@2022.09.0
|
||||
uses: home-assistant/builder@2022.11.0
|
||||
with:
|
||||
args: |
|
||||
--test \
|
||||
|
74
.github/workflows/ci.yaml
vendored
74
.github/workflows/ci.yaml
vendored
@@ -26,15 +26,15 @@ jobs:
|
||||
name: Prepare Python dependencies
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
pip install -r requirements.txt -r requirements_tests.txt
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_HOME }}
|
||||
key: |
|
||||
@@ -67,15 +67,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Register hadolint problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
||||
@@ -111,15 +111,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_HOME }}
|
||||
key: |
|
||||
@@ -155,15 +155,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -187,15 +187,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_HOME }}
|
||||
key: |
|
||||
@@ -228,15 +228,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -248,7 +248,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_HOME }}
|
||||
key: |
|
||||
@@ -272,15 +272,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -304,15 +304,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_HOME }}
|
||||
key: |
|
||||
@@ -345,9 +345,9 @@ jobs:
|
||||
name: Run tests Python ${{ needs.prepare.outputs.python-version }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
@@ -357,7 +357,7 @@ jobs:
|
||||
version: ${{ env.DEFAULT_CAS }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -392,7 +392,7 @@ jobs:
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v3.1.1
|
||||
uses: actions/upload-artifact@v3.1.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}
|
||||
path: .coverage
|
||||
@@ -403,15 +403,15 @@ jobs:
|
||||
needs: ["pytest", "prepare"]
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v4.3.0
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.11
|
||||
uses: actions/cache@v3.2.5
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3.0.0
|
||||
- uses: dessant/lock-threads@v4.0.0
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: "30"
|
||||
|
4
.github/workflows/release-drafter.yml
vendored
4
.github/workflows/release-drafter.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Release Drafter
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
echo "::set-output name=version::$datepre.$newpost"
|
||||
|
||||
- name: Run Release Drafter
|
||||
uses: release-drafter/release-drafter@v5.21.1
|
||||
uses: release-drafter/release-drafter@v5.22.0
|
||||
with:
|
||||
tag: ${{ steps.version.outputs.version }}
|
||||
name: ${{ steps.version.outputs.version }}
|
||||
|
2
.github/workflows/sentry.yaml
vendored
2
.github/workflows/sentry.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.1.0
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Sentry Release
|
||||
uses: getsentry/action-release@v1.2.1
|
||||
env:
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v6.0.1
|
||||
- uses: actions/stale@v7.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
|
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@@ -10,12 +10,12 @@ repos:
|
||||
- py310
|
||||
files: ^((supervisor|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.6.0
|
||||
- pydocstyle==6.1.1
|
||||
- flake8-docstrings==1.7.0
|
||||
- pydocstyle==6.3.0
|
||||
files: ^(supervisor|script|tests)/.+\.py$
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
@@ -24,11 +24,11 @@ repos:
|
||||
stages: [manual]
|
||||
- id: check-json
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.2.2
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py310-plus]
|
||||
|
Submodule home-assistant-polymer updated: 5bb9538861...84affcce33
@@ -1,25 +1,26 @@
|
||||
aiodns==3.0.0
|
||||
aiohttp==3.8.3
|
||||
aiohttp==3.8.4
|
||||
async_timeout==4.0.2
|
||||
atomicwrites-homeassistant==1.4.1
|
||||
attrs==22.1.0
|
||||
attrs==22.2.0
|
||||
awesomeversion==22.9.0
|
||||
brotli==1.0.9
|
||||
cchardet==2.1.7
|
||||
ciso8601==2.2.0
|
||||
ciso8601==2.3.0
|
||||
colorlog==6.7.0
|
||||
cpe==1.2.1
|
||||
cryptography==38.0.3
|
||||
debugpy==1.6.3
|
||||
cryptography==39.0.2
|
||||
debugpy==1.6.6
|
||||
deepmerge==1.1.0
|
||||
dirhash==0.2.1
|
||||
docker==6.0.1
|
||||
gitpython==3.1.29
|
||||
gitpython==3.1.31
|
||||
jinja2==3.1.2
|
||||
pulsectl==22.3.2
|
||||
pyudev==0.24.0
|
||||
ruamel.yaml==0.17.21
|
||||
securetar==2022.2.0
|
||||
sentry-sdk==1.10.1
|
||||
sentry-sdk==1.15.0
|
||||
voluptuous==0.13.1
|
||||
dbus-fast==1.74.0
|
||||
dbus-fast==1.84.1
|
||||
typing_extensions==4.3.0
|
||||
|
@@ -1,15 +1,17 @@
|
||||
black==22.10.0
|
||||
black==23.1.0
|
||||
codecov==2.1.12
|
||||
coverage==6.5.0
|
||||
flake8-docstrings==1.6.0
|
||||
flake8==5.0.4
|
||||
pre-commit==2.20.0
|
||||
pydocstyle==6.1.1
|
||||
pylint==2.15.5
|
||||
coverage==7.1.0
|
||||
flake8-docstrings==1.7.0
|
||||
flake8==6.0.0
|
||||
pre-commit==3.0.4
|
||||
pydocstyle==6.3.0
|
||||
pylint==2.15.10
|
||||
pytest-aiohttp==1.0.4
|
||||
pytest-asyncio==0.18.3
|
||||
pytest-cov==4.0.0
|
||||
pytest-timeout==2.1.0
|
||||
pytest==7.2.0
|
||||
pyupgrade==3.2.2
|
||||
time-machine==2.8.2
|
||||
pytest==7.2.1
|
||||
pyupgrade==3.3.1
|
||||
time-machine==2.9.0
|
||||
typing_extensions==4.3.0
|
||||
urllib3==1.26.14
|
@@ -158,10 +158,7 @@ class AddonManager(CoreSysAttributes):
|
||||
if not store:
|
||||
raise AddonsError(f"Add-on {slug} does not exist", _LOGGER.error)
|
||||
|
||||
if not store.available:
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {slug} not supported on this platform", _LOGGER.error
|
||||
)
|
||||
store.validate_availability()
|
||||
|
||||
self.data.install(store)
|
||||
addon = Addon(self.coresys, slug)
|
||||
@@ -263,10 +260,7 @@ class AddonManager(CoreSysAttributes):
|
||||
raise AddonsError(f"No update available for add-on {slug}", _LOGGER.warning)
|
||||
|
||||
# Check if available, Maybe something have changed
|
||||
if not store.available:
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {slug} not supported on that platform", _LOGGER.error
|
||||
)
|
||||
store.validate_availability()
|
||||
|
||||
if backup:
|
||||
await self.sys_backups.do_backup_partial(
|
||||
|
@@ -73,6 +73,7 @@ from ..jobs.decorator import Job
|
||||
from ..utils import check_port
|
||||
from ..utils.apparmor import adjust_profile
|
||||
from ..utils.json import read_json_file, write_json_file
|
||||
from ..utils.sentry import capture_exception
|
||||
from .const import (
|
||||
WATCHDOG_MAX_ATTEMPTS,
|
||||
WATCHDOG_RETRY_SECONDS,
|
||||
@@ -151,7 +152,7 @@ class Addon(AddonModel):
|
||||
_LOGGER.error(
|
||||
"Watchdog restart of addon %s failed!", addon.name
|
||||
)
|
||||
addon.sys_capture_exception(err)
|
||||
capture_exception(err)
|
||||
else:
|
||||
break
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
"""Supervisor add-on build environment."""
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -16,7 +17,7 @@ from ..const import (
|
||||
)
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..docker.interface import MAP_ARCH
|
||||
from ..exceptions import ConfigurationFileError
|
||||
from ..exceptions import ConfigurationFileError, HassioArchNotFound
|
||||
from ..utils.common import FileConfiguration, find_one_filetype
|
||||
from .validate import SCHEMA_BUILD_CONFIG
|
||||
|
||||
@@ -45,6 +46,11 @@ class AddonBuild(FileConfiguration, CoreSysAttributes):
|
||||
"""Ignore save function."""
|
||||
raise RuntimeError()
|
||||
|
||||
@cached_property
|
||||
def arch(self) -> str:
|
||||
"""Return arch of the add-on."""
|
||||
return self.sys_arch.match(self.addon.arch)
|
||||
|
||||
@property
|
||||
def base_image(self) -> str:
|
||||
"""Return base image for this add-on."""
|
||||
@@ -55,8 +61,18 @@ class AddonBuild(FileConfiguration, CoreSysAttributes):
|
||||
return self._data[ATTR_BUILD_FROM]
|
||||
|
||||
# Evaluate correct base image
|
||||
arch = self.sys_arch.match(list(self._data[ATTR_BUILD_FROM].keys()))
|
||||
return self._data[ATTR_BUILD_FROM][arch]
|
||||
if self.arch not in self._data[ATTR_BUILD_FROM]:
|
||||
raise HassioArchNotFound(
|
||||
f"Add-on {self.addon.slug} is not supported on {self.arch}"
|
||||
)
|
||||
return self._data[ATTR_BUILD_FROM][self.arch]
|
||||
|
||||
@property
|
||||
def dockerfile(self) -> Path:
|
||||
"""Return Dockerfile path."""
|
||||
if self.addon.path_location.joinpath(f"Dockerfile.{self.arch}").exists():
|
||||
return self.addon.path_location.joinpath(f"Dockerfile.{self.arch}")
|
||||
return self.addon.path_location.joinpath("Dockerfile")
|
||||
|
||||
@property
|
||||
def squash(self) -> bool:
|
||||
@@ -76,25 +92,29 @@ class AddonBuild(FileConfiguration, CoreSysAttributes):
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
"""Return true if the build env is valid."""
|
||||
return all(
|
||||
[
|
||||
self.addon.path_location.is_dir(),
|
||||
Path(self.addon.path_location, "Dockerfile").is_file(),
|
||||
]
|
||||
)
|
||||
try:
|
||||
return all(
|
||||
[
|
||||
self.addon.path_location.is_dir(),
|
||||
self.dockerfile.is_file(),
|
||||
]
|
||||
)
|
||||
except HassioArchNotFound:
|
||||
return False
|
||||
|
||||
def get_docker_args(self, version: AwesomeVersion):
|
||||
"""Create a dict with Docker build arguments."""
|
||||
args = {
|
||||
"path": str(self.addon.path_location),
|
||||
"tag": f"{self.addon.image}:{version!s}",
|
||||
"dockerfile": str(self.dockerfile),
|
||||
"pull": True,
|
||||
"forcerm": not self.sys_dev,
|
||||
"squash": self.squash,
|
||||
"platform": MAP_ARCH[self.sys_arch.match(self.addon.arch)],
|
||||
"platform": MAP_ARCH[self.arch],
|
||||
"labels": {
|
||||
"io.hass.version": version,
|
||||
"io.hass.arch": self.sys_arch.default,
|
||||
"io.hass.arch": self.arch,
|
||||
"io.hass.type": META_ADDON,
|
||||
"io.hass.name": self._fix_label("name"),
|
||||
"io.hass.description": self._fix_label("description"),
|
||||
|
@@ -1,6 +1,8 @@
|
||||
"""Init file for Supervisor add-ons."""
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Awaitable
|
||||
from collections.abc import Awaitable, Callable
|
||||
from contextlib import suppress
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -32,6 +34,7 @@ from ..const import (
|
||||
ATTR_HOST_IPC,
|
||||
ATTR_HOST_NETWORK,
|
||||
ATTR_HOST_PID,
|
||||
ATTR_HOST_UTS,
|
||||
ATTR_IMAGE,
|
||||
ATTR_INGRESS,
|
||||
ATTR_INGRESS_STREAM,
|
||||
@@ -78,10 +81,13 @@ from ..const import (
|
||||
)
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..docker.const import Capabilities
|
||||
from ..exceptions import AddonsNotSupportedError
|
||||
from .const import ATTR_BACKUP, ATTR_CODENOTARY, AddonBackupMode
|
||||
from .options import AddonOptions, UiOptions
|
||||
from .validate import RE_SERVICE, RE_VOLUME
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
Data = dict[str, Any]
|
||||
|
||||
|
||||
@@ -303,6 +309,11 @@ class AddonModel(CoreSysAttributes, ABC):
|
||||
"""Return True if add-on run on host IPC namespace."""
|
||||
return self.data[ATTR_HOST_IPC]
|
||||
|
||||
@property
|
||||
def host_uts(self) -> bool:
|
||||
"""Return True if add-on run on host UTS namespace."""
|
||||
return self.data[ATTR_HOST_UTS]
|
||||
|
||||
@property
|
||||
def host_dbus(self) -> bool:
|
||||
"""Return True if add-on run on host D-BUS."""
|
||||
@@ -595,31 +606,54 @@ class AddonModel(CoreSysAttributes, ABC):
|
||||
"""Return Signer email address for CAS."""
|
||||
return self.data.get(ATTR_CODENOTARY)
|
||||
|
||||
def validate_availability(self) -> None:
|
||||
"""Validate if addon is available for current system."""
|
||||
return self._validate_availability(self.data, logger=_LOGGER.error)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Compaired add-on objects."""
|
||||
if not isinstance(other, AddonModel):
|
||||
return False
|
||||
return self.slug == other.slug
|
||||
|
||||
def _available(self, config) -> bool:
|
||||
"""Return True if this add-on is available on this platform."""
|
||||
def _validate_availability(
|
||||
self, config, *, logger: Callable[..., None] | None = None
|
||||
) -> None:
|
||||
"""Validate if addon is available for current system."""
|
||||
# Architecture
|
||||
if not self.sys_arch.is_supported(config[ATTR_ARCH]):
|
||||
return False
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {self.slug} not supported on this platform, supported architectures: {', '.join(config[ATTR_ARCH])}",
|
||||
logger,
|
||||
)
|
||||
|
||||
# Machine / Hardware
|
||||
machine = config.get(ATTR_MACHINE)
|
||||
if machine and f"!{self.sys_machine}" in machine:
|
||||
return False
|
||||
elif machine and self.sys_machine not in machine:
|
||||
return False
|
||||
if machine and (
|
||||
f"!{self.sys_machine}" in machine or self.sys_machine not in machine
|
||||
):
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {self.slug} not supported on this machine, supported machine types: {', '.join(machine)}",
|
||||
logger,
|
||||
)
|
||||
|
||||
# Home Assistant
|
||||
version: AwesomeVersion | None = config.get(ATTR_HOMEASSISTANT)
|
||||
with suppress(AwesomeVersionException, TypeError):
|
||||
if self.sys_homeassistant.version < version:
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {self.slug} not supported on this system, requires Home Assistant version {version} or greater",
|
||||
logger,
|
||||
)
|
||||
|
||||
def _available(self, config) -> bool:
|
||||
"""Return True if this add-on is available on this platform."""
|
||||
try:
|
||||
return self.sys_homeassistant.version >= version
|
||||
except (AwesomeVersionException, TypeError):
|
||||
return True
|
||||
self._validate_availability(config)
|
||||
except AddonsNotSupportedError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _image(self, config) -> str:
|
||||
"""Generate image name from data."""
|
||||
|
@@ -70,6 +70,10 @@ def rating_security(addon: AddonModel) -> int:
|
||||
if addon.host_pid:
|
||||
rating += -2
|
||||
|
||||
# UTS host namespace allows to set hostname only with SYS_ADMIN
|
||||
if addon.host_uts and Capabilities.SYS_ADMIN in addon.privileged:
|
||||
rating += -1
|
||||
|
||||
# Docker Access & full Access
|
||||
if addon.access_docker_api or addon.with_full_access:
|
||||
rating = 1
|
||||
|
@@ -41,6 +41,7 @@ from ..const import (
|
||||
ATTR_HOST_IPC,
|
||||
ATTR_HOST_NETWORK,
|
||||
ATTR_HOST_PID,
|
||||
ATTR_HOST_UTS,
|
||||
ATTR_IMAGE,
|
||||
ATTR_INGRESS,
|
||||
ATTR_INGRESS_ENTRY,
|
||||
@@ -283,6 +284,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
||||
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_HOST_IPC, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_HOST_UTS, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_HOST_DBUS, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_DEVICES): [str],
|
||||
vol.Optional(ATTR_UDEV, default=False): vol.Boolean(),
|
||||
|
@@ -53,8 +53,10 @@ class RestAPI(CoreSysAttributes):
|
||||
self.webapp: web.Application = web.Application(
|
||||
client_max_size=MAX_CLIENT_SIZE,
|
||||
middlewares=[
|
||||
self.security.block_bad_requests,
|
||||
self.security.system_validation,
|
||||
self.security.token_validation,
|
||||
self.security.core_proxy,
|
||||
],
|
||||
handler_args={
|
||||
"max_line_size": MAX_LINE_SIZE,
|
||||
|
@@ -46,6 +46,7 @@ from ..const import (
|
||||
ATTR_HOST_IPC,
|
||||
ATTR_HOST_NETWORK,
|
||||
ATTR_HOST_PID,
|
||||
ATTR_HOST_UTS,
|
||||
ATTR_HOSTNAME,
|
||||
ATTR_ICON,
|
||||
ATTR_INGRESS,
|
||||
@@ -216,6 +217,7 @@ class APIAddons(CoreSysAttributes):
|
||||
ATTR_HOST_NETWORK: addon.host_network,
|
||||
ATTR_HOST_PID: addon.host_pid,
|
||||
ATTR_HOST_IPC: addon.host_ipc,
|
||||
ATTR_HOST_UTS: addon.host_uts,
|
||||
ATTR_HOST_DBUS: addon.host_dbus,
|
||||
ATTR_PRIVILEGED: addon.privileged,
|
||||
ATTR_FULL_ACCESS: addon.with_full_access,
|
||||
|
@@ -9,33 +9,44 @@ CONTENT_TYPE_URL = "application/x-www-form-urlencoded"
|
||||
|
||||
COOKIE_INGRESS = "ingress_session"
|
||||
|
||||
ATTR_APPARMOR_VERSION = "apparmor_version"
|
||||
ATTR_AGENT_VERSION = "agent_version"
|
||||
ATTR_APPARMOR_VERSION = "apparmor_version"
|
||||
ATTR_ATTRIBUTES = "attributes"
|
||||
ATTR_AVAILABLE_UPDATES = "available_updates"
|
||||
ATTR_BOOT_TIMESTAMP = "boot_timestamp"
|
||||
ATTR_BOOTS = "boots"
|
||||
ATTR_BROADCAST_LLMNR = "broadcast_llmnr"
|
||||
ATTR_BROADCAST_MDNS = "broadcast_mdns"
|
||||
ATTR_BY_ID = "by_id"
|
||||
ATTR_CHILDREN = "children"
|
||||
ATTR_CONNECTION_BUS = "connection_bus"
|
||||
ATTR_DATA_DISK = "data_disk"
|
||||
ATTR_DEVICE = "device"
|
||||
ATTR_DEV_PATH = "dev_path"
|
||||
ATTR_DISK_LED = "disk_led"
|
||||
ATTR_DRIVES = "drives"
|
||||
ATTR_DT_SYNCHRONIZED = "dt_synchronized"
|
||||
ATTR_DT_UTC = "dt_utc"
|
||||
ATTR_EJECTABLE = "ejectable"
|
||||
ATTR_FALLBACK = "fallback"
|
||||
ATTR_FILESYSTEMS = "filesystems"
|
||||
ATTR_HEARTBEAT_LED = "heartbeat_led"
|
||||
ATTR_IDENTIFIERS = "identifiers"
|
||||
ATTR_LLMNR = "llmnr"
|
||||
ATTR_LLMNR_HOSTNAME = "llmnr_hostname"
|
||||
ATTR_MDNS = "mdns"
|
||||
ATTR_MODEL = "model"
|
||||
ATTR_MOUNT_POINTS = "mount_points"
|
||||
ATTR_PANEL_PATH = "panel_path"
|
||||
ATTR_POWER_LED = "power_led"
|
||||
ATTR_REMOVABLE = "removable"
|
||||
ATTR_REVISION = "revision"
|
||||
ATTR_SEAT = "seat"
|
||||
ATTR_SIGNED = "signed"
|
||||
ATTR_STARTUP_TIME = "startup_time"
|
||||
ATTR_UPDATE_TYPE = "update_type"
|
||||
ATTR_USE_NTP = "use_ntp"
|
||||
ATTR_BY_ID = "by_id"
|
||||
ATTR_SUBSYSTEM = "subsystem"
|
||||
ATTR_SYSFS = "sysfs"
|
||||
ATTR_DEV_PATH = "dev_path"
|
||||
ATTR_ATTRIBUTES = "attributes"
|
||||
ATTR_CHILDREN = "children"
|
||||
ATTR_TIME_DETECTED = "time_detected"
|
||||
ATTR_UPDATE_TYPE = "update_type"
|
||||
ATTR_USE_NTP = "use_ntp"
|
||||
ATTR_VENDOR = "vendor"
|
||||
|
@@ -4,16 +4,41 @@ from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from ..const import ATTR_AUDIO, ATTR_DEVICES, ATTR_INPUT, ATTR_NAME, ATTR_OUTPUT
|
||||
from ..const import (
|
||||
ATTR_AUDIO,
|
||||
ATTR_DEVICES,
|
||||
ATTR_ID,
|
||||
ATTR_INPUT,
|
||||
ATTR_NAME,
|
||||
ATTR_OUTPUT,
|
||||
ATTR_SERIAL,
|
||||
ATTR_SIZE,
|
||||
ATTR_SYSTEM,
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..dbus.udisks2 import UDisks2
|
||||
from ..dbus.udisks2.block import UDisks2Block
|
||||
from ..dbus.udisks2.drive import UDisks2Drive
|
||||
from ..hardware.data import Device
|
||||
from .const import (
|
||||
ATTR_ATTRIBUTES,
|
||||
ATTR_BY_ID,
|
||||
ATTR_CHILDREN,
|
||||
ATTR_CONNECTION_BUS,
|
||||
ATTR_DEV_PATH,
|
||||
ATTR_DEVICE,
|
||||
ATTR_DRIVES,
|
||||
ATTR_EJECTABLE,
|
||||
ATTR_FILESYSTEMS,
|
||||
ATTR_MODEL,
|
||||
ATTR_MOUNT_POINTS,
|
||||
ATTR_REMOVABLE,
|
||||
ATTR_REVISION,
|
||||
ATTR_SEAT,
|
||||
ATTR_SUBSYSTEM,
|
||||
ATTR_SYSFS,
|
||||
ATTR_TIME_DETECTED,
|
||||
ATTR_VENDOR,
|
||||
)
|
||||
from .utils import api_process
|
||||
|
||||
@@ -21,7 +46,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def device_struct(device: Device) -> dict[str, Any]:
|
||||
"""Return a dict with information of a interface to be used in th API."""
|
||||
"""Return a dict with information of a interface to be used in the API."""
|
||||
return {
|
||||
ATTR_NAME: device.name,
|
||||
ATTR_SYSFS: device.sysfs,
|
||||
@@ -33,6 +58,42 @@ def device_struct(device: Device) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def filesystem_struct(fs_block: UDisks2Block) -> dict[str, Any]:
|
||||
"""Return a dict with information of a filesystem block device to be used in the API."""
|
||||
return {
|
||||
ATTR_DEVICE: str(fs_block.device),
|
||||
ATTR_ID: fs_block.id,
|
||||
ATTR_SIZE: fs_block.size,
|
||||
ATTR_NAME: fs_block.id_label,
|
||||
ATTR_SYSTEM: fs_block.hint_system,
|
||||
ATTR_MOUNT_POINTS: [
|
||||
str(mount_point) for mount_point in fs_block.filesystem.mount_points
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def drive_struct(udisks2: UDisks2, drive: UDisks2Drive) -> dict[str, Any]:
|
||||
"""Return a dict with information of a disk to be used in the API."""
|
||||
return {
|
||||
ATTR_VENDOR: drive.vendor,
|
||||
ATTR_MODEL: drive.model,
|
||||
ATTR_REVISION: drive.revision,
|
||||
ATTR_SERIAL: drive.serial,
|
||||
ATTR_ID: drive.id,
|
||||
ATTR_SIZE: drive.size,
|
||||
ATTR_TIME_DETECTED: drive.time_detected.isoformat(),
|
||||
ATTR_CONNECTION_BUS: drive.connection_bus,
|
||||
ATTR_SEAT: drive.seat,
|
||||
ATTR_REMOVABLE: drive.removable,
|
||||
ATTR_EJECTABLE: drive.ejectable,
|
||||
ATTR_FILESYSTEMS: [
|
||||
filesystem_struct(block)
|
||||
for block in udisks2.block_devices
|
||||
if block.filesystem and block.drive == drive.object_path
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class APIHardware(CoreSysAttributes):
|
||||
"""Handle RESTful API for hardware functions."""
|
||||
|
||||
@@ -42,7 +103,11 @@ class APIHardware(CoreSysAttributes):
|
||||
return {
|
||||
ATTR_DEVICES: [
|
||||
device_struct(device) for device in self.sys_hardware.devices
|
||||
]
|
||||
],
|
||||
ATTR_DRIVES: [
|
||||
drive_struct(self.sys_dbus.udisks2, drive)
|
||||
for drive in self.sys_dbus.udisks2.drives
|
||||
],
|
||||
}
|
||||
|
||||
@api_process
|
||||
|
@@ -85,7 +85,6 @@ class APIIngress(CoreSysAttributes):
|
||||
_LOGGER.warning("No valid ingress session %s", data[ATTR_SESSION])
|
||||
raise HTTPUnauthorized()
|
||||
|
||||
@require_home_assistant
|
||||
async def handler(
|
||||
self, request: web.Request
|
||||
) -> web.Response | web.StreamResponse | web.WebSocketResponse:
|
||||
@@ -182,6 +181,7 @@ class APIIngress(CoreSysAttributes):
|
||||
allow_redirects=False,
|
||||
data=data,
|
||||
timeout=ClientTimeout(total=None),
|
||||
skip_auto_headers={hdrs.CONTENT_TYPE},
|
||||
) as result:
|
||||
headers = _response_header(result)
|
||||
|
||||
|
@@ -1,9 +1,12 @@
|
||||
"""Handle security part of this API."""
|
||||
import logging
|
||||
import re
|
||||
from typing import Final
|
||||
from urllib.parse import unquote
|
||||
|
||||
from aiohttp.web import Request, RequestHandler, Response, middleware
|
||||
from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized
|
||||
from aiohttp.web_exceptions import HTTPBadRequest, HTTPForbidden, HTTPUnauthorized
|
||||
from awesomeversion import AwesomeVersion
|
||||
|
||||
from ...const import (
|
||||
REQUEST_FROM,
|
||||
@@ -18,11 +21,22 @@ from ...coresys import CoreSys, CoreSysAttributes
|
||||
from ..utils import api_return_error, excract_supervisor_token
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
_CORE_VERSION: Final = AwesomeVersion("2023.3.0")
|
||||
|
||||
# fmt: off
|
||||
|
||||
_CORE_FRONTEND_PATHS: Final = (
|
||||
r"|/app/.*\.(?:js|gz|json|map)"
|
||||
r"|/(store/)?addons/[^/]+/(logo|icon)"
|
||||
)
|
||||
|
||||
CORE_FRONTEND: Final = re.compile(
|
||||
r"^(?:" + _CORE_FRONTEND_PATHS + r")$"
|
||||
)
|
||||
|
||||
|
||||
# Block Anytime
|
||||
BLACKLIST = re.compile(
|
||||
BLACKLIST: Final = re.compile(
|
||||
r"^(?:"
|
||||
r"|/homeassistant/api/hassio/.*"
|
||||
r"|/core/api/hassio/.*"
|
||||
@@ -30,25 +44,27 @@ BLACKLIST = re.compile(
|
||||
)
|
||||
|
||||
# Free to call or have own security concepts
|
||||
NO_SECURITY_CHECK = re.compile(
|
||||
NO_SECURITY_CHECK: Final = re.compile(
|
||||
r"^(?:"
|
||||
r"|/homeassistant/api/.*"
|
||||
r"|/homeassistant/websocket"
|
||||
r"|/core/api/.*"
|
||||
r"|/core/websocket"
|
||||
r"|/supervisor/ping"
|
||||
r")$"
|
||||
r"|/ingress/[^/]+/.*"
|
||||
+ _CORE_FRONTEND_PATHS
|
||||
+ r")$"
|
||||
)
|
||||
|
||||
# Observer allow API calls
|
||||
OBSERVER_CHECK = re.compile(
|
||||
OBSERVER_CHECK: Final = re.compile(
|
||||
r"^(?:"
|
||||
r"|/.+/info"
|
||||
r")$"
|
||||
)
|
||||
|
||||
# Can called by every add-on
|
||||
ADDONS_API_BYPASS = re.compile(
|
||||
ADDONS_API_BYPASS: Final = re.compile(
|
||||
r"^(?:"
|
||||
r"|/addons/self/(?!security|update)[^/]+"
|
||||
r"|/addons/self/options/config"
|
||||
@@ -60,7 +76,7 @@ ADDONS_API_BYPASS = re.compile(
|
||||
)
|
||||
|
||||
# Policy role add-on API access
|
||||
ADDONS_ROLE_ACCESS = {
|
||||
ADDONS_ROLE_ACCESS: dict[str, re.Pattern] = {
|
||||
ROLE_DEFAULT: re.compile(
|
||||
r"^(?:"
|
||||
r"|/.+/info"
|
||||
@@ -111,6 +127,26 @@ ADDONS_ROLE_ACCESS = {
|
||||
),
|
||||
}
|
||||
|
||||
FILTERS: Final = re.compile(
|
||||
r"(?:"
|
||||
|
||||
# Common exploits
|
||||
r"proc/self/environ"
|
||||
r"|(<|%3C).*script.*(>|%3E)"
|
||||
|
||||
# File Injections
|
||||
r"|(\.\.//?)+" # ../../anywhere
|
||||
r"|[a-zA-Z0-9_]=/([a-z0-9_.]//?)+" # .html?v=/.//test
|
||||
|
||||
# SQL Injections
|
||||
r"|union.*select.*\("
|
||||
r"|union.*all.*select.*"
|
||||
r"|concat.*\("
|
||||
|
||||
r")",
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
@@ -121,6 +157,32 @@ class SecurityMiddleware(CoreSysAttributes):
|
||||
"""Initialize security middleware."""
|
||||
self.coresys: CoreSys = coresys
|
||||
|
||||
def _recursive_unquote(self, value: str) -> str:
|
||||
"""Handle values that are encoded multiple times."""
|
||||
if (unquoted := unquote(value)) != value:
|
||||
unquoted = self._recursive_unquote(unquoted)
|
||||
return unquoted
|
||||
|
||||
@middleware
|
||||
async def block_bad_requests(
|
||||
self, request: Request, handler: RequestHandler
|
||||
) -> Response:
|
||||
"""Process request and tblock commonly known exploit attempts."""
|
||||
if FILTERS.search(self._recursive_unquote(request.path)):
|
||||
_LOGGER.warning(
|
||||
"Filtered a potential harmful request to: %s", request.raw_path
|
||||
)
|
||||
raise HTTPBadRequest
|
||||
|
||||
if FILTERS.search(self._recursive_unquote(request.query_string)):
|
||||
_LOGGER.warning(
|
||||
"Filtered a request with a potential harmful query string: %s",
|
||||
request.raw_path,
|
||||
)
|
||||
raise HTTPBadRequest
|
||||
|
||||
return await handler(request)
|
||||
|
||||
@middleware
|
||||
async def system_validation(
|
||||
self, request: Request, handler: RequestHandler
|
||||
@@ -153,6 +215,7 @@ class SecurityMiddleware(CoreSysAttributes):
|
||||
# Ignore security check
|
||||
if NO_SECURITY_CHECK.match(request.path):
|
||||
_LOGGER.debug("Passthrough %s", request.path)
|
||||
request[REQUEST_FROM] = None
|
||||
return await handler(request)
|
||||
|
||||
# Not token
|
||||
@@ -205,3 +268,45 @@ class SecurityMiddleware(CoreSysAttributes):
|
||||
|
||||
_LOGGER.error("Invalid token for access %s", request.path)
|
||||
raise HTTPForbidden()
|
||||
|
||||
@middleware
|
||||
async def core_proxy(self, request: Request, handler: RequestHandler) -> Response:
|
||||
"""Validate user from Core API proxy."""
|
||||
if (
|
||||
request[REQUEST_FROM] != self.sys_homeassistant
|
||||
or self.sys_homeassistant.version >= _CORE_VERSION
|
||||
):
|
||||
return await handler(request)
|
||||
|
||||
authorization_index: int | None = None
|
||||
content_type_index: int | None = None
|
||||
user_request: bool = False
|
||||
admin_request: bool = False
|
||||
ingress_request: bool = False
|
||||
|
||||
for idx, (key, value) in enumerate(request.raw_headers):
|
||||
if key in (b"Authorization", b"X-Hassio-Key"):
|
||||
authorization_index = idx
|
||||
elif key == b"Content-Type":
|
||||
content_type_index = idx
|
||||
elif key == b"X-Hass-User-ID":
|
||||
user_request = True
|
||||
elif key == b"X-Hass-Is-Admin":
|
||||
admin_request = value == b"1"
|
||||
elif key == b"X-Ingress-Path":
|
||||
ingress_request = True
|
||||
|
||||
if user_request or admin_request:
|
||||
return await handler(request)
|
||||
|
||||
is_proxy_request = (
|
||||
authorization_index is not None
|
||||
and content_type_index is not None
|
||||
and content_type_index - authorization_index == 1
|
||||
)
|
||||
|
||||
if (
|
||||
not CORE_FRONTEND.match(request.path) and is_proxy_request
|
||||
) or ingress_request:
|
||||
raise HTTPBadRequest()
|
||||
return await handler(request)
|
||||
|
@@ -1,14 +1,14 @@
|
||||
|
||||
function loadES5() {
|
||||
var el = document.createElement('script');
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.4dd28383.js';
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.5c6aba93.js';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
|
||||
loadES5();
|
||||
} else {
|
||||
try {
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.962b867f.js')")();
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.499355be.js')")();
|
||||
} catch (err) {
|
||||
loadES5();
|
||||
}
|
||||
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/02a7a490.js
Normal file
1
supervisor/api/panel/frontend_es5/02a7a490.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/02a7a490.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/02a7a490.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/047c3be2.js
Normal file
1
supervisor/api/panel/frontend_es5/047c3be2.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/047c3be2.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/047c3be2.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/05d391fd.js
Normal file
1
supervisor/api/panel/frontend_es5/05d391fd.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/05d391fd.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/05d391fd.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/06fc6167.js
Normal file
1
supervisor/api/panel/frontend_es5/06fc6167.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/06fc6167.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/06fc6167.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
2
supervisor/api/panel/frontend_es5/0845e41a.js
Normal file
2
supervisor/api/panel/frontend_es5/0845e41a.js
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
BIN
supervisor/api/panel/frontend_es5/0845e41a.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/0845e41a.js.gz
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
||||
!function(){"use strict";var r,t,e={5425:function(r,t,e){var n=e(93217);e(58556);function o(r,t){return function(r){if(Array.isArray(r))return r}(r)||function(r,t){var e=null==r?null:"undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(null==e)return;var n,o,u=[],i=!0,a=!1;try{for(e=e.call(r);!(i=(n=e.next()).done)&&(u.push(n.value),!t||u.length!==t);i=!0);}catch(f){a=!0,o=f}finally{try{i||null==e.return||e.return()}finally{if(a)throw o}}return u}(r,t)||function(r,t){if(!r)return;if("string"==typeof r)return u(r,t);var e=Object.prototype.toString.call(r).slice(8,-1);"Object"===e&&r.constructor&&(e=r.constructor.name);if("Map"===e||"Set"===e)return Array.from(r);if("Arguments"===e||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e))return u(r,t)}(r,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(r,t){(null==t||t>r.length)&&(t=r.length);for(var e=0,n=new Array(t);e<t;e++)n[e]=r[e];return n}var i={filterData:function(r,t,e){return e=e.toUpperCase(),r.filter((function(r){return Object.entries(t).some((function(t){var n=o(t,2),u=n[0],i=n[1];return!(!i.filterable||!String(i.filterKey?r[i.valueColumn||u][i.filterKey]:r[i.valueColumn||u]).toUpperCase().includes(e))}))}))},sortData:function(r,t,e,n){return r.sort((function(r,o){var u=1;"desc"===e&&(u=-1);var i=t.filterKey?r[t.valueColumn||n][t.filterKey]:r[t.valueColumn||n],a=t.filterKey?o[t.valueColumn||n][t.filterKey]:o[t.valueColumn||n];return"string"==typeof i&&(i=i.toUpperCase()),"string"==typeof a&&(a=a.toUpperCase()),void 0===i&&void 0!==a?1:void 0===a&&void 0!==i?-1:i<a?-1*u:i>a?1*u:0}))}};(0,n.Jj)(i)}},n={};function o(r){var t=n[r];if(void 0!==t)return t.exports;var u=n[r]={exports:{}};return e[r](u,u.exports,o),u.exports}o.m=e,o.x=function(){var r=o.O(void 0,[9191],(function(){return o(5425)}));return r=o.O(r)},r=[],o.O=function(t,e,n,u){if(!e){var i=1/0;for(c=0;c<r.length;c++){e=r[c][0],n=r[c][1],u=r[c][2];for(var a=!0,f=0;f<e.length;f++)(!1&u||i>=u)&&Object.keys(o.O).every((function(r){return o.O[r](e[f])}))?e.splice(f--,1):(a=!1,u<i&&(i=u));if(a){r.splice(c--,1);var l=n();void 0!==l&&(t=l)}}return t}u=u||0;for(var c=r.length;c>0&&r[c-1][2]>u;c--)r[c]=r[c-1];r[c]=[e,n,u]},o.n=function(r){var t=r&&r.__esModule?function(){return r.default}:function(){return r};return o.d(t,{a:t}),t},o.d=function(r,t){for(var e in t)o.o(t,e)&&!o.o(r,e)&&Object.defineProperty(r,e,{enumerable:!0,get:t[e]})},o.f={},o.e=function(r){return Promise.all(Object.keys(o.f).reduce((function(t,e){return o.f[e](r,t),t}),[]))},o.u=function(r){return"ed6e5677.js"},o.o=function(r,t){return Object.prototype.hasOwnProperty.call(r,t)},o.p="/api/hassio/app/frontend_es5/",function(){var r={5425:1,5477:1};o.f.i=function(t,e){r[t]||importScripts(o.p+o.u(t))};var t=self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[],e=t.push.bind(t);t.push=function(t){var n=t[0],u=t[1],i=t[2];for(var a in u)o.o(u,a)&&(o.m[a]=u[a]);for(i&&i(o);n.length;)r[n.pop()]=1;e(t)}}(),t=o.x,o.x=function(){return o.e(9191).then(t)};o.x()}();
|
Binary file not shown.
2
supervisor/api/panel/frontend_es5/0977644e.js
Normal file
2
supervisor/api/panel/frontend_es5/0977644e.js
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
BIN
supervisor/api/panel/frontend_es5/0977644e.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/0977644e.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/0dbf7d96.js
Normal file
1
supervisor/api/panel/frontend_es5/0dbf7d96.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/0dbf7d96.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/0dbf7d96.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/10eda328.js
Normal file
1
supervisor/api/panel/frontend_es5/10eda328.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/10eda328.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/10eda328.js.gz
Normal file
Binary file not shown.
2
supervisor/api/panel/frontend_es5/1138ca90.js
Normal file
2
supervisor/api/panel/frontend_es5/1138ca90.js
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
BIN
supervisor/api/panel/frontend_es5/1138ca90.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1138ca90.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
2
supervisor/api/panel/frontend_es5/16b7d5e1.js
Normal file
2
supervisor/api/panel/frontend_es5/16b7d5e1.js
Normal file
File diff suppressed because one or more lines are too long
19
supervisor/api/panel/frontend_es5/16b7d5e1.js.LICENSE.txt
Normal file
19
supervisor/api/panel/frontend_es5/16b7d5e1.js.LICENSE.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-LIcense-Identifier: Apache-2.0
|
||||
*/
|
BIN
supervisor/api/panel/frontend_es5/16b7d5e1.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/16b7d5e1.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/17bf1723.js
Normal file
1
supervisor/api/panel/frontend_es5/17bf1723.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/17bf1723.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/17bf1723.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/1b298732.js
Normal file
1
supervisor/api/panel/frontend_es5/1b298732.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1b298732.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1b298732.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/1cab7924.js
Normal file
1
supervisor/api/panel/frontend_es5/1cab7924.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1cab7924.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1cab7924.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/1d98d880.js
Normal file
1
supervisor/api/panel/frontend_es5/1d98d880.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1d98d880.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1d98d880.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
2
supervisor/api/panel/frontend_es5/1f74f712.js
Normal file
2
supervisor/api/panel/frontend_es5/1f74f712.js
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
BIN
supervisor/api/panel/frontend_es5/1f74f712.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1f74f712.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/20708ea7.js
Normal file
1
supervisor/api/panel/frontend_es5/20708ea7.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/20708ea7.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/20708ea7.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
2
supervisor/api/panel/frontend_es5/26f4e401.js
Normal file
2
supervisor/api/panel/frontend_es5/26f4e401.js
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user