Skip to content

Conversation

@irisjhu
Copy link
Contributor

@irisjhu irisjhu commented Jul 10, 2025

included the option to have roles for remote actors/resources (aka no foreign key) - not sure how robust this approach is with letting users specify the type name. we would also lose the type entry for sql_types, which we could add a custom field for as well, but that starts to get a little harder to validate with types and all.

also didn't do function wrapping (signature forwarding) for the actor and resource orm functions due to the issue i discovered previously with the additional arguments not playing well with positional arguments.

@irisjhu irisjhu marked this pull request as ready for review July 10, 2025 15:11
@irisjhu irisjhu requested a review from vrama628 July 10, 2025 15:11
Copy link
Collaborator

@vrama628 vrama628 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! I like this approach; more general than trying to go through SQLAlchemy's many-to-many relationship, since it seems likely that people may have role-assignment tables that they don't explicitly use a many-to-many relationship for. This looks mostly good to me; one small blocking comment and a bunch of other considerations that you can take or leave as you see fit.

we would also lose the type entry for sql_types

Could we just get the sql type from the column type? Doesn't seem like we'd need to the user to specify the sql type since we already have that on-hand.

We can fix the positional arg issues as a separate PR. Do we have a linear ticket for that? I wonder if we can use / in the call signature to indicate that our arguments should only be accepted as kwargs and shouldn't conflict with positional args.

organization: Mapped["Organization"] = relationship()
role: Mapped["Role"] = relationship()

class Agent(Base, Resource):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought (non-blocking): I wonder if we should add a class Actor(Resource) mixin for better symmetry with Oso types here, so that you could declare the Agent model as an Actor instead of a Resource

tests/models.py Outdated
id: Mapped[str] = mapped_column(String(50), primary_key=True)
description: Mapped[str]

class AgentOrganizationRole(Base, Resource, RoleMapping):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (blocking): to me it doesn't make sense for AgentOrganizationRole to be a Resource; it seems to correspond to facts but not to resources. There is no corresponding resource AgentOrganizationRole {...} in the policy, nor should there be.

It seems like the if not issubclass(mapper.class_, Resource): continue line in oso.py would need to be changed given that we now need to process models that aren't resources.

"""
pass

class RoleMapping:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is it unnecessary to have Mapping in this name? It seems redundant or inconsistent. Does "mapping" mean a mapping from ORM models to role assignments (in which case it would seem like Resource would have to be named ResourceMapping for the naming to be consistent) or does it mean mapping from users to resources via roles? In which case I would think "Assignment" may be more fitting than "Mapping", but still not really necessary. Another idea would be to name it HasRole, to make it clearer that it maps to has_role facts

col.column.info[_REMOTE_RELATION_INFO_KEY] = (remote_resource_name, remote_relation_key)
return col

def actor(*args, actor_type: Optional[str] = None, **kwargs) -> MappedColumn:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: I wonder if it would make sense for these functions to be class methods on the Role[Mapping] class, to make it clearer what they're doing. E.g. agent_id = Role.actor(String(50), ...). A function named actor on its own seems a bit general/ambiguous to a reader who doesn't know it's connected to the Role[Mapping] mixin

return Value("Agent", "1")

@pytest.fixture
def bellerophon():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL

Comment on lines 143 to 144
elif _ROLE_MAPPING_ROLE_INFO_KEY in attr.columns[0].info:
role_column = attr.columns[0]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought (non-blocking): Hmm, I wonder if the role value should also get the same treatment wrt possibly having a different type. Sometimes roles are declares as a Role resource rather than strings, particularly when doing custom roles. I think it may make sense to do that IFF the role model is a Resource, and use String if not. With custom roles it is common to have an app-side table with information about the roles, like you have in the test example, since they're dynamically created by users. While we're at it, it may make sense to sanity-check that the actor/resource foreign keys are subclasses of Resource as well, especially if that allows for code that can be applied homogeneously across all three args.

Comment on lines 119 to 125
if len(foreign_keys) == 0:
if column.info[info_key] is None:
raise ValueError(f"Oso role mapping {role_mapping_type} type must be provided if it is not a foreign key")
oso_type = column.info[info_key]
else:
fk = foreign_keys[0].column
oso_type = _get_classname_by_table(fk.table, registry)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this seems like it allows for a confusing case where the user supplies a custom actor_type="Something" on a column that has a foreign key (perhaps to a non-oso-resource table/model), and we just silently ignore their configuration. Would it make sense to either prefer the user-supplied type or loudly error if there's both a foreign key and configured type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it makes sense to loudly error so the behavior is explicit. also kind of a workaround for the positional arguments issue - users can't use both a foreign key and a user-supplied type (kwarg).

@irisjhu irisjhu requested a review from vrama628 July 17, 2025 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants