When designing classes, how do you deal with sub-types? - oop

I'm learning about object oriented design as I go through my first real, albeit personal, project.
I'm at the stage where I'm going to build a user object. I have 4 user subtypes:
root-admin - webmaster, site owner
group-admin - leader of a group that has a membership and page (e.g. band manager)
group-member - user that can view, post, and comment as group member on the page and see private content (e.g. band member)
unassociated user - member of the entire site that can view and comment publicly (e.g. fan)
All group admins will be group members and all group members will be unassociated users as well. So basically it's a simple user permissions hierarchy.
With that said, how do I go about setting up the architecture of my class with respect to these levels? Is there a nesting technique? I'm just struggling to hash out the scaffolding here. I have my use cases and narratives written out already.

So you're implementing role-based access control. Each user has one of four roles. So a user is an object and the users's role is one of the attributes in a user object.
enum roles {
root_admin,
group_admin,
group_member,
unassociated_user
}
class user {
string id;
roles role;
}
Next, you need to enforce the business rules that allow users of certain roles to do certain activities.
Start with a simple solution (this is always a good policy):
enum activities {
view,
post,
add_comment
}
And implement a function or class whose job is to say if an activity is allowed for a given role.
class role_based_access_control {
private:
permissions perm;
public:
bool is_permitted(activities a, roles r) {
return perm[r].contain(a);
}
}
Then, in places in your code where you implement different activities, include a call to this function:
void add_comment_to_page(user u, comment c, page p) {
if (!role_base_access_control.is_permitted(add_comment,u.role))
throw "access_forbidden";
...
}
The important thing is to keep the role based access control rules centralized so that they are visible, easy to monitor, and to audit.

Related

RBAC with Ory Keto and ownership of objects

I'm trying to achieve the following points with Ory Keto:
UserA has ownership of ProductA -> Ownership gives one CRUD rights.
UserB has the role Admin -> Admin gives one CRUD rights on everything.
UserA has a role KYCVerified or is part of a group named KYCVerified -> This gives the user additional permissions.
Point 1 describes the concept of ownership, which is described as one of the shortcomings of RBAC (source) with the current state of Ory Keto:
There is no concept of ownership: Dan is the author of article "Hello
World" and is thus allowed to update it.
Point 2 describes a role that basically passes the ownership check, since this role can do everything. This should be possible in the current state, but not in combination with point 1.
Point 3 describes basically the same thing as point 2, but this is more specific to my use case.
I've read this article on the Ory website and this article. However, I'm still unable to wrap my head around this concept. I've made the following example of how I see the concept of ownership with Ory Keto:
# Tenant TenantA needs to be a owner of product ProductA in order to view it
products:ProductA#view#(tenants:TenantA#owner)
# Tenant A is a owner of ProductA
tenants:ProductA#owner#TenantA
But this will result in a lot of rules and I'm not even sure if this is the way to go.
As of this moment you are right. You have to create a bunch of tuples manually. The full set of tuples should be something like:
products:ProductA#owner#UserA
products:ProductA#crud#(products:ProductA#owner)
roles:admin#member#UserB
products:ProductA#curd#(roles:admin#member)
products:ProductA#additional_permissions#(roles:KYCVerified#member)
roles:KYCVerified#member#UserA
With https://github.com/ory/keto/pull/877 you will be able to define global rewrites. It would looks similar to:
import { Context, Namespace } from #ory/keto-config
/**
* "User" is a namespace with no additional rewrite rules defined.
*/
class User implements Namespace {}
/**
* "Role"s only have members.
*/
class Role implements Namespace {
related: {
members: User[]
}
}
/**
* "Product" is a namespace representing a product. It has some rewrites.
*/
class Product implements Namespace {
// Relations are defined and type-annotated.
related: {
/**
* "owners" are the users that are the owners of the product.
*/
owners: User[]
/**
* "admins" are the roles that are administrators of this product (potentially only one).
*/
admins: Role[]
/**
* "special_roles" are the roles a user has to be member of to gain "additional_permissions"
*/
special_roles: Role[]
}
permits = {
// this is probably three/four rewrites (create, read, update, delete) with similar rules
crud: (ctx: Context): boolean =>
this.related.owners.includes(ctx.subject) ||
this.related.admins.some((admin) => admin.related.members.includes(ctx.subject)),
// for the additional_permissions one has to have curd and be member of a special role
additional_permissions: (ctx: Context): boolean =>
this.permits.crud(ctx) &&
this.related.special_roles.some((role) => role.related.members.includes(ctx.subject))
}
}
With that you have to create these tuples:
products:ProductA#owners#UserA
roles:admin#members#UserB
roles:KYCVerified#members#UserA
products:ProductA#admins#(roles:admin)
products:ProductA#additional_permissions#(roles:KYCVerified)
Please note that it is not possible (and not planned right now) to define a single admin group that would have access to everything. You always have to have some kind of relation between the object and subject to query/rewrite it. That is the reason for having the admins and special_roles relations.

How can I discover all the roles a Perl 6 type does?

With .does I can check if a type has the role I already know. I'd like to get the list of roles. Inheritance has .^mro but I didn't see anything like that for roles in the meta model stuff.
Along with that, given a "type", how can I tell if it was defined as a class or a role?
.^roles
say Rat.^roles; # ((Rational[Int,Int]) (Real) (Numeric))
By default it includes every role, including roles brought in by other roles. To only get the first level use :!transitive
Rat.^roles(:!transitive); # ((Rational[Int,Int]))
There's already a good answer to the first question. About the second one, each meta-object has an archetypes method that in turn carries a range of properties of the types represented by that meta-type. This exists because Perl 6 is open to new meta-types (which might be easier to think about as "types of type"); probably the most widely used example of this today is OO::Monitors. The archetypes are more focused on what one can do with the type. For example:
> role R { }; say "C: {.composable} I: {.inheritable}" given R.HOW.archetypes;
C: 1 I: 0
> class C { }; say "C: {.composable} I: {.inheritable}" given C.HOW.archetypes;
C: 0 I: 1
The set of available properties can be introspected:
> Int.HOW.archetypes.^methods(:local)
(nominal nominalizable inheritable inheritalizable composable
composalizable generic parametric coercive definite augmentable)
For example, "nominal" means "can this serve as a nominal type", and "augmentable" means "is it allowed to augment this kind of type". The things like "inheritalizable" mean "can I inheritalize such a type" - that is, turn it into a type that I can inherit from even if I can't inherit from this type. A role is not inheritable, but it is inheritalizable, and the inheritalize operation on it will produce the pun of the role. This is what is happening under the hood when writing something like class C is SomeRole { }, and means that not only is Perl 6 open to new types of type, but those new types of type can describe how they want to work, if at all, with inheritance and composition.
Being composable with does is probably the main defining property of a role, and thus the composable property is likely the best one to use when asking "is this a role". It is also possible to look at the type of the meta-object, as suggested in another answer, but there are multiple meta-objects involved in representing roles (the short name role group, a currying of that group with parameters, and an individual role, plus an internal concretization form that supports the composition process).
> say (role RRR[::T] { }).HOW.^name
Perl6::Metamodel::ParametricRoleHOW
> say RRR.HOW.^name
Perl6::Metamodel::ParametricRoleGroupHOW
> say RRR[Int].HOW.^name
Perl6::Metamodel::CurriedRoleHOW
Thus it's rather more robust to simply check if the thing is composable.
> say (role RRR[::T] { }).HOW.archetypes.composable
1
> say RRR.HOW.archetypes.composable
1
> say RRR[Int].HOW.archetypes.composable
1
Along with that, given a "type", how can I tell if it was defined as a class or a role?
A class is a type whose meta class is of type Metamodel::ClassHOW:
sub type-classify(Mu \t) {
given t.HOW {
return 'class' when Metamodel::ClassHOW;
return 'role' when Metamodel::ParametricRoleGroupHOW;
}
return 'other';
}
say type-classify(Int); # class
say type-classify(Rational); # role
say type-classify(Bool); # other
Regarding your second question,
given a "type", how can I tell if it was defined as a class or a role?
I haven't found a direct way of doing that. Both classes and roles have Mu in their hierarchy, so that will not distinguish them. However, only classes get to be recognized by (the curiously named) MetaModel::ClassHOW. So we can hack something like this:
role Ur { }
role F does Ur { }
class G does F { }
for Ur, F, G -> $class-or-role {
CATCH {
default {
say "not classy";
}
}
$class-or-role.say;
$class-or-role.^mro.say;
}
Which will print:
(Ur)
not classy
(F)
not classy
(G)
((G) (Any) (Mu))
, since calling ^mro on a role will raise an exception. That can be turned into a function for printing out which one is a role, and which is not.

Akka.net carry on information within an actor hierarchy

I'm trying to pass on certain information about a hierarchy of actors so that's available when they receive messages. For example, I have one actor chain per user and I would like to make available the user information (name) across all children of that actor.
At the moment I'm trying to access the user actor from any children in the hierarchy to get the name but a) I don't know if that's a good practice and b) A simply don't know if I can achieve that when I have more than one level in the hierarchy and the user name is obviously dynamic.
So I've tried this (assume I want to access the parent's parent)
var name = Context.ActorSelection("../..").Path.Name;
That doesn't return anything useful and doesn't seem to go up two levels in the hierarchy.
The other option I thought was creating the actor hierarchy and construct all the nodes below with Props and passing in the user name and again, I don't know if that's a good practice either/the right thing to do. So for example:
public class MyActor: TypedActor
{
public MyActor(string id)
{
_id = id;
Context.ActorOf(Props.Create<ChildActor>(_id), "childname");
}
}
And so on...
I don't know what kind of problem you are going to solve, but I could suggest to not couple userId in actor constructor (props)
in root actor you could store a list (dict) with user actors reference and users id, then in child actor using <PRESTART> action ask for user ID.
protected override void PreStart()
{
Context.Parent.Tell(new GetUserIdMessage());
base.PreStart();
}
then add Receive<UserIdMessage> and store this in a field inside an actor
This will allow write code without coupling and this extra few messages shouldn't have impact on system performance

how to recognize object's responsibility?

I'm new in OOP and I just started learning it. Its too complicated to determine the functionality of classes. Let's take an example:
We have an Address-book and an user want to add a new contact to it.
In this scenario we have 2 classes:
User: that determine the user that logged in.
Contact: A contact object that consists of Name, Address, Phone Number, etc
And the questions:
Who have to save a new contact?User class or Contact Class
If we try to check the user's permission before doing anything where is the best place for it?
Is it OK that these classes have a access to database?(Is it better to create 3rd class for doing query stuffs?)
Thanks for any good idea ;)
Usable distribution of "responsibility" is an OOP design and architecture decision with no single simple correct answer. For discussion refer to Stack Overflow question What is the single most influential book every programmer should read?
You'll learn the pros/cons by coding (using someone's design or creating your own design which does not work well).
However there are some useful/frequent distributions of responsibility already known as http://en.wikipedia.org/wiki/Software_design_pattern
In my opinion the only fixed fact is that each class/function/structure should have its responsibility clearly defined/documented - since the very first lines of code - and "do one thing and do it well"
Contacts are user specific. Thus every user object (class instance) should contain its own contacts object which is a container of contact (other user) objects, comprising in turn of name, address, phone etc.
class User {
String name;
String phone;
String address;
Contacts contacts;
....
}
class Contacts {
List<User> items;
}
The Contacts class should have the implementation of saving a new contact, which needs to be called from a User method, something like the following.
User u;
Contacts c = u.getContacts();
c.addContact(name, address, phone);
User's permissions should be checked in the User class.
The methods of these classes should interface with the database. For this each class method can open a new connection to a database and execute SQL queries. Example method of User cass:
User getContact(String name) {
Connection conn = getConnection();
....
PreparedStatement ps = con.prepareStatement("select * from Contacts where name = ?");
...
return userRcd;
}
1) Save new contact must the separate class, which working directly with database
2) Best place to check user permission - in user class of course
3) See the item 1:)
I recommend you get strong knowledge about SOLID principles, it's basics for good design.

Combination of roles in Episerver

I have a site where our customers log in to see their own data. Each customer must only see their own data (of course), and different users will have access to different pages within one customer. In addition - the editors must see all data.
I want to set up the access rights based on roles to determine which customer that the user is member of, and what pages the user can access.
Groups:
Customer1Role
Customer2Role
TicketViewerRole
ChangeRequestRole
Users:
Cust1_LowLevelUser. Roles: Customer1Role, TicketViewerRole
Cust1_HighLevelUser Roles: Customer1Role, TicketViewRole, ChangeRequestRole
Cust2_LowLevelUser. Roles: Customer2Role, TicketViewerRole
Cust2_HighLevelUser Roles: Customer2Role, TicketViewRole, ChangeRequestRole
Page structure
We have created a page tree where each customer has its own "root page" with access only to their respective role. Below that node we create instances of the data specific pages, which have their access rights based on user roles as well as the customer role.
Customer1 (Customer1Role)
|--TicketsForCust1 (Customer1Role, TicketViewerRole)
|--ChangeRequestsForCust1 (Customer1Role, ChangeRequestRole)
Customer2 (Customer2Role)
|--TicketsForCust2 (Customer2Role, TicketViewerRole)
|--ChangeRequestsForCust2 (Customer2Role, ChangeRequestRole)
Burning question:
How do we prevent user Cust2_HighLevelUser from seeing ChangeRequestsForCust1?
EPiServer only checks if any role is sufficient for granting access, and since the user belongs to ChangeRequestRole, they will be granted access, regardless of the customer specific role. Is it possible to make EPiServer check BOTH the customer role, and the page role?
Or do I have to look at this from another view? Please let me know if you have run into this and solved it in another way.
Sorry, long post, but hopefully I get my point across.
There is no Deny flag in the access rights model so you need to code it yourself with that role structure.
Add code to your template base class that denies access and for the PageTree control you can do something like this:
protected void NavSubPageTreeFilter(object sender, EPiServer.Filters.FilterEventArgs e)
{
for (int i = e.Pages.Count - 1; i > -1; i--)
{
PageData pd = e.Pages[i];
if (yourUser.IsInRole("blabla") && ... etc)
{
e.Pages.RemoveAt(i);
}
}
}