We sat down to speak with Harley Lang, who’s been working on Intraverbal, a new content creation platform for educators and students. Intraverbal has a novel way of constructing lessons, and both students and teachers use the platform. Harley had been hand-rolling his authorization code, and when it started to get unmanageable, he reached for Oso to simplify his authorization design. Intraverbal is an exciting project, and we at Oso were very interested to see the role the Oso library played in the app!
What is Intraverbal?
Intraverbal provides students with supercharged educational resources — textbooks that are dynamic, not static. Students are asked to complete problems and quizzes as they go through the text. Rather than just getting a few tries at a quiz before failing, Intraverbal lets you practice what you need to know before trying again.
Intraverbal also provides teachers with a suite of tools they can use to create lessons. Teachers can start with text, and as their lesson plans grow, they can embed documents and videos. Intraverbal lets teachers construct a comprehensive lesson plan, with many kinds of media and built-in repeated practice. All the work that teachers do to assess student competency is automated away.
Why did you choose to work on Intraverbal?
My day job is as a behavioral analyst. I've learned and been trained in the best practices of pedagogy, and I wanted to design software that included some of those best practices.
How did you implement authorization before adding Oso?
I don't have a computer science background — I came into this with the desire to create the tools that I wanted to use to teach. I quickly found out that authorization was something I needed to deal with. I realized — not every user should be able to access every user's object. Not every curriculum should be accessible to other teachers to edit. How was I going to prevent that?
When I realized I needed to deal with this, I looked around for how to do that. For someone new to coding, options like Google Zanzibar are intimidating — I had no idea how to go about adding that to my app.
Instead of doing that, I wrote a lot of switch statements trying to determine who had access to what. It was all written by intuition and by example — I'd think, if this user is trying to access this other user’s lesson, what are the cases in which they should be granted access?
Of course, as my API grew, there got to be more and more duplication of that logic, and it was cumbersome to work with. When I refactored that spaghetti, I fell backward into a role-based access model.
What was it like to add Oso to your app?
It was quite nice! First of all, it allowed me to rip out a whole bunch of lines of code.
I could delete the abstractions that I'd come up with. It was a treat, because at the end of the refactor process, I was able to whittle down my authorization code to one line: allow(user, action, resource)
. I no longer have to think hard about it, and it's much more readable.
Now, when I'm editing an API, I can see clearly what's needed. I know everything is going to be safe to touch, because that authorization logic is already taken care of. It's allowed me to have a really nice scheme of how I lay out my API routes.
I'm looking at my polar file right now, and it's so easy to read, because it's immediately apparent what rules affect which actions. It makes for a really simple way to lay out auth rules in one file. That's something I didn't have before. I was putting all my auth checks scattered throughout my API, and I wasn't able to see them in one list. And of course, the logic was more complex at the time!
About how long did that take?
It took a little less than three days. It was a very quick refactor. I think it would go much quicker now, because Oso helped me think about authorization in terms of roles, and I’m now more familiar with the Polar language.
What's your workflow like when you add a new authorization rule?
I'll start with the Jest test framework, and write several tests for what the rule should do. To link things up, I’ll add an Express API endpoint, then run the tests to make sure they don't pass. Then I'll edit the Polar rule, and make sure the tests pass. This has made it really easy to make sure my rules are doing what I expect! The whole process usually takes between five and ten minutes.
Could you walk us through your policy file and how that gets used in your app?
Sure! When someone’s accessing a lesson, our backend queries this rule:
allow(actor: UserClass, action: String, resource: LessonClass) if
user_in_role_for_resource(actor, role, resource) and
role_allow(role, action, resource);
That is to say, I check that the user has a particular role, and that the role has access to that resource.
For each lesson, we have writer
, manager
, and admin
roles. Each of these roles are derived from the user_in_role_for_resource(actor, role, resource)
call above. Here is what that rule looks like:
user_in_role_for_resource(user, role, resource) if
role = resource.getCollaboratorRole(user.user);
The above code passes UserClass.user
to the LessonClass.getCollaboratorRole()
function to check if the user has a role. The resulting role is propagated back up to the original rule and provided to role_allow(role, action, resource)
.
Next, the role_allow(role, action, resource)
function will check to see if the user can perform the requested action. Here are a few actions that can be performed with each role:
# writer actions:
role_allow("writer", "write", _resource: LessonClass);
# manager actions:
role_allow("manager", action: String, resource: LessonClass) if
role_allow("writer", action, resource); # managers can perform writer actions
role_allow("manager", "advanced_settings_create_coupon", _resource: LessonClass);
# admin actions:
role_allow("admin", action: String, resource: LessonClass) if
role_allow("writer", action, resource) or # admin can perform writer actions
role_allow("manager", action, resource); # admin can perform manager actions
role_allow("admin", "advanced_settings_clone", _resource: LessonClass);
role_allow("admin", "advanced_settings_create_collaborator", _resource: LessonClass);
These role_allow()
definitions are:
Writers have write
access. Managers and admins can also write
, as they inherit the actions that can be performed by writers.
Writers cannot add coupons to a lesson with advanced_settings_create_coupon
, but managers and admins can.
Likewise, writers and managers cannot add collaborators or clone the lesson, but admins can.
If you are not a writer, manager, or admin, all actions will be denied, because Oso is deny-by-default.
[Interviewer’s Note: For more on Harley’s work here, see Harley’s post in our Slack “show-and-tell” channel!]
Oso is a different programming paradigm than most people are used to. Was using a logic programming language confusing at all?
There were some pieces that were tricky at first. I remember asking [Oso programmer] Leina about the role_allow
function, which was tripping me up at the time. I didn't understand how role
data flowed through a policy file. At first it felt magical, but the language logic was immediately clear once I understood how roles propagated through rules.
When I run into a sticking point with the Polar language, it guides me to think about authorization differently. For instance, it can get tricky to manage complex conditionals in Polar. But every time I've run into that problem, it's guided me to break everything out into custom rules and then make decisions at a higher level.
Do you have any tips for someone who's starting to use Oso?
My golden advice is to do what I described above — write a test first, and then make the rule you're writing match the test. As my application grew and as I learned the Polar language, testing ensured that I could quickly roll out new functionality without having to manually test each authorization use case.
[Interviewer's note — we at Oso agree! Writing your authorization rules test-first can be very helpful in making sure your rules are as simple as possible]
Is there anything in particular that you'd like to see from Oso?
I mean, you all already do so much. It's quite amazing to watch. It's actually kind of addicting. I check the Oso Slack every day. I don't need to, but I do because it's so cool to see new people coming into the community, and how quickly everyone on your team is responsive to them. Some of the stuff that people in the community are working on are way over my head. It's amazing to see that you have a team that has so much breadth that someone from Oso will be able to answer any given question. Like, I have no idea how controllers in .NET work! But your team can answer questions about that.
I feel like Oso’s support really knocks it out of the park. That's why I love engaging with you all, because you go the extra mile to really help everyone. I think the thing everyone should know is that your community is amazing, and it'll probably be a much easier refactor to include Oso than any other library, because the level of support is bar none. Everyone in the community wants to help everyone else.
I don't think there's anything about the library I would change. You all continue to knock it out of the park with everything you do.
Thank you!
If you have any questions for Harley, join us on slack and chat with him directly! And if you'd like to try out Oso, or are interested in learning how you'd use it in your application schedule a 1 on 1 with one of our engineers!