Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Verifica is Ruby's most scalable authorization solution ready to handle sophisticated authorization rules.
current_user
, external service, API client, you name itVerifica is designed around Access Control List. ACL powers a straightforward and unified authorization flow for any user and resource, regardless of how complex the authorization rules are.
Note: Verifica is a new open-source gem, so you may wonder if it's reliable. Internally, this solution has been battle-tested in several B2B products, including one with over 15M database records. But DYOR anyway.
Let's say you are working on a video platform application:
Given all these, how do you even find a list of videos available for current_user
?
Bunch of if/elsif
and enormous SQL query with many joins? Is there a better way? Verifica shines for this kind of problem.
In the Real-world example with Rails you can see the solution in detail.
require "verifica"
User = Struct.new(:id, :role, keyword_init: true) do
# Verifica expects each security subject to respond to #subject_id, #subject_type, and #subject_sids
alias_method :subject_id, :id
def subject_type = :user
def subject_sids(**)
role == "root" ? ["root"] : ["authenticated", "user:#{id}"]
end
end
Video = Struct.new(:id, :author_id, :public, keyword_init: true) do
# Verifica expects each secured resource to respond to #resource_id, and #resource_type
alias_method :resource_id, :id
def resource_type = :video
end
video_acl_provider = lambda do |video, **|
Verifica::Acl.build do |acl|
acl.allow "root", [:read, :write, :delete, :comment]
acl.allow "user:#{video.author_id}", [:read, :write, :delete, :comment]
if video.public
acl.allow "authenticated", [:read, :comment]
end
end
end
authorizer = Verifica.authorizer do |config|
config.register_resource :video, [:read, :write, :delete, :comment], video_acl_provider
end
public_video = Video.new(id: 1, author_id: 1000, public: true)
private_video = Video.new(id: 2, author_id: 1000, public: false)
superuser = User.new(id: 777, role: "root")
video_author = User.new(id: 1000, role: "user")
other_user = User.new(id: 2000, role: "user")
authorizer.authorized?(superuser, private_video, :delete) # => true
authorizer.authorized?(video_author, private_video, :delete) # => true
authorizer.authorized?(other_user, private_video, :read) # => false
authorizer.authorized?(other_user, public_video, :comment) # => true
begin
# raises Verifica::AuthorizationError: Authorization FAILURE. Subject 'user' id='2000'. Resource 'video' id='1'. Action 'write'
authorizer.authorize(other_user, public_video, :write)
rescue Verifica::AuthorizationError => e
e.explain # => Long-form explanation of why action is not authorized, your debugging friend
end
# #authorization_result returns a special object with a bunch of useful info
auth_result = authorizer.authorization_result(superuser, private_video, :delete)
auth_result.success? # => true
auth_result.subject_id # => 777
auth_result.resource_type # => :video
auth_result.action # => :delete
auth_result.allowed_actions # => [:read, :write, :delete, :comment]
auth_result.explain # => Long-form explanation of why action is authorized
Required Ruby version >= 3.0
Install the gem and add to the application's Gemfile by executing:
$ bundle add verifica
Get a high-level overview of Verifica's core concepts and architecture before diving into usage nuances. Verifica may appear complex initially, but it prioritizes explicitness, flexibility, and scalability over nice looking magic. Here is an explanation of each component:
Security subject is a user, process, or system granted access to specific resources.
In most applications the subject is currently authenticated user, aka current_user
.
In code a subject could be represented by any object that responds to #subject_id
, #subject_type
, and #subject_sids
.
class User
def subject_id
123
end
def subject_type
:user
end
def subject_sids
["root"] # see Security Identifier section below to understand what is this for
end
end
Resource refers to anything that requires protection. In most applications resources are entities stored in the database, such as Post, Comment, User, etc.
In code a resource could be represented by any object that responds to #resource_id
and #resource_type
.
class Post
def resource_id
1
end
def resource_type
:post
end
end
Action that Subject can perform on a protected Resource. Represented as a Symbol in code,
it could be traditional :read
, :write
, :delete
or more domain specific :comment
, :publish
, etc.
SID is a value used to identify and differentiate Subjects and assign access rights based on the subject's attributes like role, organization, group, or country.
In code SID could be represented by immutable string (other objects work too, equality check is the only requirement). Each subject has one or more SIDs.
superuser.subject_sids # => ["root"]
moderator_user.subject_sids # => ["user:321", "role:moderator"]
regular_user.subject_sids # => ["authenticated", "user:123", "country:UA"]
organization_user.subject_sids # => ["authenticated", "user:456", "country:UA", "org:789"]
anonymous_user.subject_sids # => ["anonymous", "country:UA"]
ACL consists of Access Control Entries (ACEs) and defines which actions are allowed or denied for particular SIDs. ACL is associated with a specific protected resource in your system.
video_acl = Verifica::Acl.build do |acl|
acl.allow "authenticated", [:read, :comment]
acl.deny "country:US", [:read]
end
video_acl.to_a
# =>
# [#<Verifica::Ace:0x00007fab1955dd60 @action=:read, @allow=true, @sid="authenticated">,
# #<Verifica::Ace:0x00007fab1955dd10 @action=:comment, @allow=true, @sid="authenticated">,
# #<Verifica::Ace:0x00007fab1955dc48 @action=:read, @allow=false, @sid="country:US">]
AclProvider is an object that responds to #call(resource, **)
and returns ACL for the given resource.
class VideoAclProvider
def call(video, **context)
Verifica::Acl.build do |acl|
acl.allow "user:#{video.author_id}", [:read, :write, :delete, :comment]
if video.public?
acl.allow "authenticated", [:read, :comment]
end
end
end
end
And finally, Authorizer, the heart of Verifica. It couples all concepts above into an isolated container with no global state. Each Authorizer has a list of resource types registered with their companion AclProviders and several methods to check the Subject's rights to perform a specific action on a given resource.
Check the Basic example above to see how it all plays together.
Demo: https://verifica-rails-example.maximgurin.com
Let's say you started working on your next big thing idea — a video hosting application. In the beginning, you have only 2 user types and straightforward rules:
class Video
scope :available_for, ->(user) do
where(public: true).or(where(author_id: user.id)) unless user.admin?
end
end
class VideosController
def index
@videos = Video.available_for(current_user)
end
end
Time goes by and 4 years later you have:
Wow, that's a pretty extensive list of requirements. Easy to get lost!
Now the most exciting part. How do you implement Video.available_for
method with so many details to consider?
Videos table is big, so you can't use SQL joins to, let's say, check the video author's organization or other dependencies.
And even if you can, a query with so many joins and conditions would be write-only anyway :)
Here is how this challenge could be resolved using Verifica and ACL:
# app/acl_providers/video_acl_provider.rb
class VideoAclProvider
include Verifica::Sid
POSSIBLE_ACTIONS = [:read, :write, :delete].freeze
def call(video, **)
Verifica::Acl.build do |acl|
acl.allow root_sid, POSSIBLE_ACTIONS
acl.allow user_sid(video.author_id), POSSIBLE_ACTIONS
acl.allow role_sid("moderator"), [:read, :delete]
next if video.draft?
ds = video.distribution_setting
author_org = video.author.organization
allowed_countries = author_org&.allow_countries || ds.allow_countries
denied_countries = author_org&.deny_countries || ds.deny_countries
# ...and 30 more lines to handle all our requirements
end
end
end
# config/initializers/verifica.rb
require "verifica"
# Quick and dirty way for simplicity
# In the real app, you could use DI container to hold configured Verifica::Authorizer instance
Rails.configuration.after_initialize do
AUTHORIZER = Verifica.authorizer do |config|
config.register_resource :video, VideoAclProvider::POSSIBLE_ACTIONS, VideoAclProvider.new
end
end
# app/models/user.rb
class User < ApplicationRecord
include Verifica::Sid
alias_method :subject_id, :id
def subject_type = :user
def subject_sids(**)
case role
when "root"
[root_sid]
when "moderator"
[user_sid(id), role_sid("moderator")]
when "user"
sids = [authenticated_sid, user_sid(id), country_sid(country)]
organization_id.try { |org_id| sids.push(organization_sid(org_id)) }
sids
when "organization_admin"
sids = [authenticated_sid, user_sid(id), country_sid(country)]
sids.push(organization_sid(organization_id))
sids.push(role_sid("organization_admin:#{organization_id}"))
else
throw RuntimeError("Unsupported user role: #{role}")
end
end
end
What we've done:
Verifica::Authorizer
object. It's available as AUTHORIZER
constant anywhere in the app:video
type as a secured resource. VideoAclProvider
defines rules, who can do whatUser
to be a security Subject. Each user has list of Security Identifiers depending on the role and other attributesNow, a few last steps and the challenge resolved:
# db/migrate/20230113203815_add_read_sids_to_videos.rb
# For simplicity, we are adding two String array columns directly to videos table.
# In the real app, you could use something like ElasticSearch to hold videos with these companion columns
class AddReadSidsToVideos < ActiveRecord::Migration[7.0]
def change
add_column :videos, :read_allow_sids, :string, null: false, array: true, default: [], index: true
add_column :videos, :read_deny_sids, :string, null: false, array: true, default: [], index: true
end
end
# app/models/video.rb
class Video < ApplicationRecord
attr_accessor :allowed_actions
alias_method :resource_id, :id
before_save :update_read_acl
def resource_type = :video
def update_read_acl
acl = AUTHORIZER.resource_acl(self)
self.read_allow_sids = acl.allowed_sids(:read)
self.read_deny_sids = acl.denied_sids(:read)
end
# And finally, this is our goal. Straightforward implementation regardless of how complex the rules are.
scope :available_for, ->(user) do
sids = user.subject_sids
where("read_allow_sids && ARRAY[?]::varchar[]", sids).where.not("read_deny_sids && ARRAY[?]::varchar[]", sids)
end
end
# app/controllers/videos_controller
class VideosController
def index
@videos = Video
.includes(:distribution_setting, author: [:organization])
.available_for(current_user)
.order(:name)
.limit(50)
end
def show
@video = Video.find(params[:id])
# upon successful authorization helper object is returned with a bunch of useful info
auth_result = AUTHORIZER.authorize(current_user, @video, :read)
# add list of allowed actions so the frontend knows whether show "Edit" and "Delete" buttons, for example
@video.allowed_actions = auth_result.allowed_actions
end
def destroy
video = Video.find(params[:id])
AUTHORIZER.authorize(current_user, video, :delete)
video.destroy
end
end
Voila, we're done! So now, no matter how sophisticated our authorization rules are, we have a clear method to find available videos for any user. No conditions, no special handling for superusers as everyone goes through the unified mechanism.
Important points not covered in this example but needed in the real app:
read_allow_sids
, read_deny_sids
columns.
Same applies to Distribution Settings and other dependencies.VideoAclProvider
changed you need to run a background job
to update read_allow_sids
, read_deny_sids
columns for all videos.See also:
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests.
You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
.
To release a new version, update the version number in version.rb
, and then run bundle exec rake release
,
which will create a git tag for the version, push git commits and the created tag,
and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/maximgurin/verifica. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Verifica project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
FAQs
Unknown package
We found that verifica demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.