Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/ISSUE_TEMPLATE/task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Task
description: For staff only - Create a task
type: Task
body:
- type: markdown
attributes:
value: |
## ⚠️ RESTRICTED ACCESS

**This form is restricted to Open Home Foundation staff, authorized contributors, and integration code owners only.**

If you are a community member wanting to contribute, please:
- For bug reports: Use the [bug report form](https://github.com/home-assistant/core/issues/new?template=bug_report.yml)
- For feature requests: Submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)

---

### For authorized contributors

Use this form to create tasks for development work, improvements, or other actionable items that need to be tracked.
- type: textarea
id: description
attributes:
label: Task description
description: |
Provide a clear and detailed description of the task that needs to be accomplished.

Be specific about what needs to be done, why it's important, and any constraints or requirements.
placeholder: |
Describe the task, including:
- What needs to be done
- Why this task is needed
- Expected outcome
- Any constraints or requirements
validations:
required: true
- type: textarea
id: additional_context
attributes:
label: Additional context
description: |
Any additional information, links, research, or context that would be helpful.

Include links to related issues, research, prototypes, roadmap opportunities etc.
placeholder: |
- Roadmap opportunity: [links]
- Feature request: [link]
- Technical design documents: [link]
- Prototype/mockup: [link]
validations:
required: false
84 changes: 84 additions & 0 deletions .github/workflows/restrict-task-creation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Restrict task creation

# yamllint disable-line rule:truthy
on:
issues:
types: [opened]

jobs:
check-authorization:
runs-on: ubuntu-latest
# Only run if this is a Task issue type (from the issue form)
if: github.event.issue.issue_type == 'Task'
steps:
- name: Check if user is authorized
uses: actions/github-script@v7
with:
script: |
const issueAuthor = context.payload.issue.user.login;

// First check if user is an organization member
try {
await github.rest.orgs.checkMembershipForUser({
org: 'home-assistant',
username: issueAuthor
});
console.log(`✅ ${issueAuthor} is an organization member`);
return; // Authorized, no need to check further
} catch (error) {
console.log(`ℹ️ ${issueAuthor} is not an organization member, checking codeowners...`);
}

// If not an org member, check if they're a codeowner
try {
// Fetch CODEOWNERS file from the repository
const { data: codeownersFile } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: 'CODEOWNERS',
ref: 'dev'
});

// Decode the content (it's base64 encoded)
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf-8');

// Check if the issue author is mentioned in CODEOWNERS
// GitHub usernames in CODEOWNERS are prefixed with @
if (codeownersContent.includes(`@${issueAuthor}`)) {
console.log(`✅ ${issueAuthor} is a integration code owner`);
return; // Authorized
}
} catch (error) {
console.error('Error checking CODEOWNERS:', error);
}

// If we reach here, user is not authorized
console.log(`❌ ${issueAuthor} is not authorized to create Task issues`);

// Close the issue with a comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `Hi @${issueAuthor}, thank you for your contribution!\n\n` +
`Task issues are restricted to Open Home Foundation staff, authorized contributors, and integration code owners.\n\n` +
`If you would like to:\n` +
`- Report a bug: Please use the [bug report form](https://github.com/home-assistant/core/issues/new?template=bug_report.yml)\n` +
`- Request a feature: Please submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)\n\n` +
`If you believe you should have access to create Task issues, please contact the maintainers.`
});

await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed'
});

// Add a label to indicate this was auto-closed
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['auto-closed']
});
22 changes: 9 additions & 13 deletions homeassistant/components/androidtv_remote/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,18 @@
from asyncio import timeout
import logging

from androidtvremote2 import (
AndroidTVRemote,
CannotConnect,
ConnectionClosed,
InvalidAuth,
)

from homeassistant.config_entries import ConfigEntry
from androidtvremote2 import CannotConnect, ConnectionClosed, InvalidAuth

from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady

from .helpers import create_api, get_enable_ime
from .helpers import AndroidTVRemoteConfigEntry, create_api, get_enable_ime

_LOGGER = logging.getLogger(__name__)

PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.REMOTE]

AndroidTVRemoteConfigEntry = ConfigEntry[AndroidTVRemote]


async def async_setup_entry(
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
Expand Down Expand Up @@ -82,13 +74,17 @@ def on_hass_stop(event: Event) -> None:
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
) -> bool:
"""Unload a config entry."""
_LOGGER.debug("async_unload_entry: %s", entry.data)
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)


async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_update_options(
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
) -> None:
"""Handle options update."""
_LOGGER.debug(
"async_update_options: data: %s options: %s", entry.data, entry.options
Expand Down
43 changes: 30 additions & 13 deletions homeassistant/components/androidtv_remote/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntry,
SOURCE_RECONFIGURE,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
Expand All @@ -33,20 +33,14 @@
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo

from .const import CONF_APP_ICON, CONF_APP_NAME, CONF_APPS, CONF_ENABLE_IME, DOMAIN
from .helpers import create_api, get_enable_ime
from .helpers import AndroidTVRemoteConfigEntry, create_api, get_enable_ime

_LOGGER = logging.getLogger(__name__)

APPS_NEW_ID = "NewApp"
CONF_APP_DELETE = "app_delete"
CONF_APP_ID = "app_id"

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("host"): str,
}
)

STEP_PAIR_DATA_SCHEMA = vol.Schema(
{
vol.Required("pin"): str,
Expand All @@ -67,7 +61,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
"""Handle the initial and reconfigure step."""
errors: dict[str, str] = {}
if user_input is not None:
self.host = user_input[CONF_HOST]
Expand All @@ -76,15 +70,32 @@ async def async_step_user(
await api.async_generate_cert_if_missing()
self.name, self.mac = await api.async_get_name_and_mac()
await self.async_set_unique_id(format_mac(self.mac))
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data={
CONF_HOST: self.host,
CONF_NAME: self.name,
CONF_MAC: self.mac,
},
)
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
# Likely invalid IP address or device is network unreachable. Stay
# in the user step allowing the user to enter a different host.
errors["base"] = "cannot_connect"
else:
user_input = {}
default_host = user_input.get(CONF_HOST, vol.UNDEFINED)
if self.source == SOURCE_RECONFIGURE:
default_host = self._get_reconfigure_entry().data[CONF_HOST]
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
step_id="reconfigure" if self.source == SOURCE_RECONFIGURE else "user",
data_schema=vol.Schema(
{vol.Required(CONF_HOST, default=default_host): str}
),
errors=errors,
)

Expand Down Expand Up @@ -217,10 +228,16 @@ async def async_step_reauth_confirm(
errors=errors,
)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration."""
return await self.async_step_user(user_input)

@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
config_entry: AndroidTVRemoteConfigEntry,
) -> AndroidTVRemoteOptionsFlowHandler:
"""Create the options flow."""
return AndroidTVRemoteOptionsFlowHandler(config_entry)
Expand All @@ -229,7 +246,7 @@ def async_get_options_flow(
class AndroidTVRemoteOptionsFlowHandler(OptionsFlow):
"""Android TV Remote options flow."""

def __init__(self, config_entry: ConfigEntry) -> None:
def __init__(self, config_entry: AndroidTVRemoteConfigEntry) -> None:
"""Initialize options flow."""
self._apps: dict[str, Any] = dict(config_entry.options.get(CONF_APPS, {}))
self._conf_app_id: str | None = None
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/androidtv_remote/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from homeassistant.const import CONF_HOST, CONF_MAC
from homeassistant.core import HomeAssistant

from . import AndroidTVRemoteConfigEntry
from .helpers import AndroidTVRemoteConfigEntry

TO_REDACT = {CONF_HOST, CONF_MAC}

Expand Down
6 changes: 4 additions & 2 deletions homeassistant/components/androidtv_remote/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

from androidtvremote2 import AndroidTVRemote, ConnectionClosed

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity import Entity

from .const import CONF_APPS, DOMAIN
from .helpers import AndroidTVRemoteConfigEntry


class AndroidTVRemoteBaseEntity(Entity):
Expand All @@ -23,7 +23,9 @@ class AndroidTVRemoteBaseEntity(Entity):
_attr_has_entity_name = True
_attr_should_poll = False

def __init__(self, api: AndroidTVRemote, config_entry: ConfigEntry) -> None:
def __init__(
self, api: AndroidTVRemote, config_entry: AndroidTVRemoteConfigEntry
) -> None:
"""Initialize the entity."""
self._api = api
self._host = config_entry.data[CONF_HOST]
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/androidtv_remote/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from .const import CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE

AndroidTVRemoteConfigEntry = ConfigEntry[AndroidTVRemote]


def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRemote:
"""Create an AndroidTVRemote instance."""
Expand All @@ -23,6 +25,6 @@ def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRem
)


def get_enable_ime(entry: ConfigEntry) -> bool:
def get_enable_ime(entry: AndroidTVRemoteConfigEntry) -> bool:
"""Get value of enable_ime option or its default value."""
return entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE)
2 changes: 1 addition & 1 deletion homeassistant/components/androidtv_remote/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["androidtvremote2"],
"requirements": ["androidtvremote2==0.2.2"],
"requirements": ["androidtvremote2==0.2.3"],
"zeroconf": ["_androidtvremote2._tcp.local."]
}
Loading
Loading