Authorization

Lean CMS uses two layers of authorization, deliberately. They solve different problems and don’t substitute for each other.

Layer Used for Provided by
Area gates “Is this user allowed in /lean-cms at all? In the blog editor? In settings?” LeanCms::Authorization controller concern — simple before_actions that check permission flags on the user.
Per-record decisions “Can this admin modify that super-admin? Can this editor delete a post they didn’t author?” Pundit policies — LeanCms::PostPolicy, LeanCms::UserPolicy, etc.

Both layers are already wired into the gem’s CMS controllers. You only need to think about them when you build your own controllers that interact with Lean CMS-managed records or live inside the admin area.

The permission flags

The User model carries these boolean columns (see Installation step 4):

Flag Powers
is_super_admin Implicit true for every permission below.
can_edit_pages /lean-cms/page-contents/*, inline-edit endpoints.
can_edit_blog /lean-cms/posts/* (blog + portfolio).
can_manage_users /lean-cms/users/*.
can_access_settings /lean-cms/settings/*, /lean-cms/notification_settings/*.

The corresponding User predicates (can_edit_pages?, etc.) must return true whenever the user is a super admin. The recommended pattern:

def can_edit_pages?
  is_super_admin? || can_edit_pages
end

has_any_cms_permission? is the master gate — return true if the user has any of the four area permissions or is a super admin.

Layer 1: Area gates (LeanCms::Authorization)

LeanCms::Authorization is the controller concern included automatically by LeanCms::ApplicationController. It installs:

before_action :require_cms_access

…which redirects to / with a flash alert if current_user&.has_any_cms_permission? is false. Inside the gem, specific areas tighten this with:

before_action :require_page_editing       # in LeanCms::PageContentsController
before_action :require_blog_editing       # in LeanCms::PostsController
before_action :require_user_management    # in LeanCms::UsersController
before_action :require_settings_access    # in LeanCms::SettingsController

Using area gates in your own controllers

If you build a custom controller that should only be reachable by users with a specific CMS permission, include the concern and add the right before_action:

class Admin::ReportsController < ApplicationController
  include LeanCms::Authorization
  before_action :require_settings_access
end

Available before-action helpers: require_cms_access, require_page_editing, require_blog_editing, require_user_management, require_settings_access.

Layer 2: Per-record decisions (Pundit)

Within an area the user is allowed into, finer questions still need answers:

  • “Editors can edit any post, but only super admins can edit posts other editors wrote.”
  • “A user manager can deactivate any user — except themselves, and except super admins.”
  • “A non-author can read drafts only if they have super-admin rights.”

These are record-level decisions, and they live in Pundit policies under app/policies/lean_cms/. Example:

class LeanCms::PostPolicy < LeanCms::ApplicationPolicy
  def update?
    return false unless user.can_edit_blog?
    user.is_super_admin? || record.author_id == user.id
  end

  def destroy? = update?
end

Inside any Lean CMS admin controller, you can call:

authorize @post           # runs PostPolicy#update? (or whatever action maps)
policy_scope(LeanCms::Post)  # returns the subset the user is allowed to see
policy(@post).update?     # boolean check, no exception

Using Pundit in your own controllers and views

Pundit is already a dependency of Lean CMS and the gem’s policies are namespaced. If you have your own record types that should respect CMS permissions, drop a policy under app/policies/:

class TestimonialPolicy < ApplicationPolicy
  def update?
    user.can_edit_pages?  # reuse a Lean CMS permission flag
  end
end

…and in your controller:

class TestimonialsController < ApplicationController
  include LeanCms::Authorization
  before_action :require_page_editing   # area gate
  before_action :set_testimonial, only: %i[ edit update ]
  before_action -> { authorize @testimonial }, only: %i[ edit update ]   # per-record
end

When to use which

Question Answer Use
“Can this user see this area at all?” Yes/no based on permission flags. LeanCms::Authorization before_action.
“Can this user perform this action on this record?” Depends on record state, ownership, or the user’s relationship to it. Pundit policy.
“Should I show or hide this UI element for this user?” A view question. policy(record).update? in the view.
“Which records should be in this list?” A scoping question. policy_scope(Model) — the policy’s Scope#resolve.

If you find yourself reaching for Pundit to express “this user has the permission to edit pages,” that’s an area gate, not a record decision — prefer require_page_editing. If you find yourself reaching for a permission flag to express “but only on posts they wrote,” that’s a per-record decision — write a policy.

Why the hybrid

A Pundit policy that ignores record to answer “does this user have permission X?” is policy busywork. A before_action that has to fan-thread record ownership through controller code is controller busywork. Keeping the two layers separate keeps each one declarative.