Files
clinch/app/controllers/admin/applications_controller.rb
Dan Milne 03dfdbd83a
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / scan_container (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled
Default-deny access control with group flags and access enumeration
Replaces the implicit "empty allowed_groups means public" rule with
explicit default-deny across both OIDC and ForwardAuth. Adds two boolean
flags on Group — auto_assign (Keycloak-style auto-join on user create)
and admin (members can reach the admin panel) — and drops the
users.admin column entirely. Adds "Users with access" and "Accessible
applications" panels with via-group badges on the application/user show
pages.

BEHAVIOR CHANGE: a ForwardAuth app with no allowed_groups previously
bypassed authentication entirely; it now returns 403 like any other
unauthorized request. The data migration seeds an "everyone" group and
attaches it to all previously group-less apps to preserve behavior on
existing installs. An "admins" group is seeded and backfilled from any
user with the old admin column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-07 15:53:27 +10:00

131 lines
4.2 KiB
Ruby

module Admin
class ApplicationsController < BaseController
before_action :set_application, only: [:show, :edit, :update, :destroy, :regenerate_credentials]
def index
@applications = Application.order(created_at: :desc)
end
def show
@allowed_groups = @application.allowed_groups
@users_with_access = User.where(status: User.statuses[:active])
.joins(groups: :applications)
.where(applications: {id: @application.id})
.distinct
.includes(:groups)
.order(:email_address)
end
def new
@application = Application.new
@available_groups = Group.order(:name)
end
def create
@application = Application.new(application_params)
@available_groups = Group.order(:name)
if @application.save
# Handle group assignments
if params[:application][:group_ids].present?
group_ids = params[:application][:group_ids].reject(&:blank?)
@application.allowed_groups = Group.where(id: group_ids)
end
# Get the plain text client secret to show one time (confidential clients only)
client_secret = nil
if @application.oidc? && @application.confidential_client?
client_secret = @application.generate_new_client_secret!
end
flash[:notice] = "Application created successfully."
if @application.oidc?
flash[:client_id] = @application.client_id
flash[:client_secret] = client_secret if client_secret
flash[:public_client] = true if @application.public_client?
end
redirect_to admin_application_path(@application)
else
render :new, status: :unprocessable_entity
end
end
def edit
@available_groups = Group.order(:name)
end
def update
if @application.update(application_params)
# Handle group assignments
if params[:application][:group_ids].present?
group_ids = params[:application][:group_ids].reject(&:blank?)
@application.allowed_groups = Group.where(id: group_ids)
else
@application.allowed_groups = []
end
redirect_to admin_application_path(@application), notice: "Application updated successfully."
else
@available_groups = Group.order(:name)
render :edit, status: :unprocessable_entity
end
end
def destroy
@application.destroy
redirect_to admin_applications_path, notice: "Application deleted successfully."
end
def regenerate_credentials
if @application.oidc?
# Generate new client ID (always)
new_client_id = SecureRandom.urlsafe_base64(32)
@application.update!(client_id: new_client_id)
flash[:notice] = "Credentials regenerated successfully."
flash[:client_id] = @application.client_id
# Generate new client secret only for confidential clients
if @application.confidential_client?
client_secret = @application.generate_new_client_secret!
flash[:client_secret] = client_secret
else
flash[:public_client] = true
end
redirect_to admin_application_path(@application)
else
redirect_to admin_application_path(@application), alert: "Only OIDC applications have credentials."
end
end
private
def set_application
@application = Application.find(params[:id])
end
def application_params
permitted = params.require(:application).permit(
:name, :slug, :app_type, :active, :redirect_uris, :description, :metadata,
:domain_pattern, :landing_url, :access_token_ttl, :refresh_token_ttl, :id_token_ttl,
:icon, :backchannel_logout_uri, :is_public_client, :require_pkce, :skip_consent
)
# Handle headers_config - it comes as a JSON string from the text area
if params[:application][:headers_config].present?
begin
permitted[:headers_config] = JSON.parse(params[:application][:headers_config])
rescue JSON::ParserError
permitted[:headers_config] = {}
end
end
# Remove client_secret from params if present (shouldn't be updated via form)
permitted.delete(:client_secret)
permitted
end
end
end