Skip to content

Commit

Permalink
HDM API (#153)
Browse files Browse the repository at this point in the history
* Add role to users #87

Role can be either `admin`, `regular` or `api`. The first
two represent the two existing roles while the third is
added in preparation for API access.

* First steps towards an API #87

Allows to query for environments, nodes and keys for now.

Returned JSON structure might not yet be final.

* Allow querying key data w/o environment #87

This will simply use the node's environment.
  • Loading branch information
oneiros authored Aug 10, 2023
1 parent abf43f6 commit c99a7b3
Show file tree
Hide file tree
Showing 31 changed files with 1,789 additions and 81 deletions.
37 changes: 0 additions & 37 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ Layout/FirstArrayElementIndentation:
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
Layout/HashAlignment:
Exclude:
- 'app/models/user.rb'
- 'test/models/key_test.rb'

# Offense count: 1
Expand Down Expand Up @@ -164,7 +163,6 @@ Layout/SpaceBeforeComma:
# SupportedStylesForEmptyBraces: space, no_space
Layout/SpaceInsideHashLiteralBraces:
Exclude:
- 'config/routes.rb'
- 'test/models/hiera_data/data_file_test.rb'
- 'test/models/hiera_data/interpolation_test.rb'
- 'test/models/hiera_data/yaml_file_test.rb'
Expand Down Expand Up @@ -210,13 +208,6 @@ Lint/UnusedBlockArgument:
Exclude:
- 'app/models/key.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods.
Lint/UnusedMethodArgument:
Exclude:
- 'test/test_helper.rb'

# Offense count: 4
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
Metrics/AbcSize:
Expand Down Expand Up @@ -259,13 +250,6 @@ Naming/RescuedExceptionsVariableName:
Exclude:
- 'app/models/hiera_data/config.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: NilOrEmpty, NotPresent, UnlessPresent.
Rails/Blank:
Exclude:
- 'app/models/ability.rb'

# Offense count: 1
# This cop supports unsafe auto-correction (--auto-correct-all).
# Configuration parameters: Whitelist, AllowedMethods, AllowedReceivers.
Expand Down Expand Up @@ -337,7 +321,6 @@ Style/ClassAndModuleChildren:
- 'test/models/hiera_data/git_repo_test.rb'
- 'test/models/hiera_data/interpolation_test.rb'
- 'test/models/hiera_data/yaml_file_test.rb'
- 'test/test_helper.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
Expand Down Expand Up @@ -447,12 +430,6 @@ Style/RaiseArgs:
Exclude:
- 'app/models/hiera_data/git_repo.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
Style/RedundantBegin:
Exclude:
- 'test/test_helper.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
Style/RedundantCondition:
Expand All @@ -466,20 +443,6 @@ Style/RedundantFetchBlock:
Exclude:
- 'config/puma.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
Style/RedundantSelf:
Exclude:
- 'app/models/user.rb'

# Offense count: 1
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Exclude:
- 'config/routes.rb'

# Offense count: 3
# This cop supports safe auto-correction (--auto-correct).
# Configuration parameters: EnforcedStyle.
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ group :test do
gem 'selenium-webdriver'
# Easy installation and use of web drivers to run system tests with browsers
gem 'webdrivers'
gem 'rspec-openapi'
end

group :linter do
Expand Down
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@ GEM
connection_pool
regexp_parser (2.8.1)
rexml (3.2.5)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-openapi (0.8.0)
actionpack (>= 5.2.0)
rspec-core
rspec-support (3.12.0)
rubocop (1.49.0)
json (~> 2.3)
parallel (~> 1.10)
Expand Down Expand Up @@ -460,6 +466,7 @@ DEPENDENCIES
puppetdb-ruby
rails (~> 7.0.0)
redis (~> 5.0)
rspec-openapi
rubocop
rubocop-capybara
rubocop-rails
Expand Down
45 changes: 45 additions & 0 deletions app/controllers/api/v1/api_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module Api
module V1
class ApiController < ApplicationController
attr_reader :current_user

rescue_from Hdm::Error, with: :return_error_json
rescue_from CanCan::AccessDenied, with: :access_denied

before_action :authentication_required

helper_method :current_user

private

def authentication_required
@current_user =
if Rails.configuration.hdm.authentication_disabled
DummyUser.new
else
authenticate_with_http_basic do |email, password|
u = User.api.find_by(email: email.downcase)
u&.authenticate(password)
end
end
access_denied unless @current_user
end

def load_environments
@environments = Environment.all
@environments.select! { |e| current_user.may_access?(e) }
@environment = Environment.find(params[:environment_id])
authorize! :show, @environment
end

def return_error_json(error)
@error = error
render json: error.message, status: :internal_server_error
end

def access_denied
render json: "forbidden", status: :forbidden
end
end
end
end
16 changes: 16 additions & 0 deletions app/controllers/api/v1/environments_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Api
module V1
class EnvironmentsController < Api::V1::ApiController
def index
@environments = Environment.all
@environments.select! { |e| current_user.may_access?(e) }

respond_to do |format|
format.json do
render json: @environments
end
end
end
end
end
end
51 changes: 51 additions & 0 deletions app/controllers/api/v1/keys_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module Api
module V1
class KeysController < Api::V1::ApiController
before_action :load_environment_and_node

def index
@keys = Key.all_for(@node, environment: @environment)
@keys.select! { |k| current_user.may_access?(k) }

respond_to do |format|
format.json do
render json: @keys.to_json(except: :environment)
end
end
end

def show
@key = Key.new(environment: @environment, name: params[:id])
authorize! :show, @key

respond_to do |format|
format.json do
render json: values_per_hierarchy_and_file
end
end
end

private

def values_per_hierarchy_and_file
@environment.hierarchies.map do |hierarchy|
files = hierarchy.files_for(node: @node).map do |file|
{ path: file.path, value: file.value_for(key: @key).value }
end
{ hierarchy_name: hierarchy.name, files: }
end
end

def load_environment_and_node
@node = Node.find(params[:node_id])
authorize! :show, @node
@environment = if params[:environment_id].present?
Environment.find(params[:environment_id])
else
@node.environment
end
authorize! :show, @environment
end
end
end
end
16 changes: 16 additions & 0 deletions app/controllers/api/v1/nodes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Api
module V1
class NodesController < Api::V1::ApiController
def index
@nodes = Node.all
@nodes.select! { |n| current_user.may_access?(n) }

respond_to do |format|
format.json do
render json: @nodes
end
end
end
end
end
end
21 changes: 16 additions & 5 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
module ApplicationHelper
FLASH_CLASSES = {
"notice" => "alert alert-success",
"error" => "alert alert-danger",
"alert" => "alert alert-warning"
}.freeze
ROLE_BADGE_COLORS = {
"admin" => "badge-danger",
"regular" => "badge-success",
"api" => "badge-info"
}.freeze

def flash_class(level)
case level
when 'notice' then "alert alert-success"
when 'error' then "alert alert-danger"
when 'alert' then "alert alert-warning"
end
FLASH_CLASSES[level]
end

def format_path(file, key)
Expand All @@ -30,4 +37,8 @@ def user_deletion_confirmation(user)
'Are you sure?'
end
end

def role_badge_color(role)
ROLE_BADGE_COLORS[role]
end
end
21 changes: 16 additions & 5 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class Ability
include CanCan::Ability

# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
def initialize(user)
# Define abilities for the passed in user here. For example:
#
Expand Down Expand Up @@ -35,7 +35,7 @@ def initialize(user)
if User.none?
can :create, User
else
return unless user.present?
return if user.blank?

if user.admin?
if User.admins.count > 1
Expand All @@ -46,9 +46,8 @@ def initialize(user)
can :manage, User, id: User.where.not(id: user.id).ids
can :create, User, id: nil
can :manage, Group
end

if user.user?
elsif user.user?
can :read, User, id: user.id
can :update, User, id: user.id
can :manage, Environment do |environment|
Expand All @@ -61,8 +60,20 @@ def initialize(user)
user.may_access?(key)
end
can :manage, Value

elsif user.api?
can :read, Environment do |environment|
user.may_access?(environment)
end
can :read, Node do |node|
user.may_access?(node)
end
can :read, Key do |key|
user.may_access?(key)
end
can :read, Value
end
end
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
end
4 changes: 4 additions & 0 deletions app/models/dummy_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def user?
true
end

def api?
true
end

def may_access?(_record)
true
end
Expand Down
1 change: 1 addition & 0 deletions app/models/hiera_model.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class HieraModel
include ActiveModel::Model
include ActiveModel::Attributes
include ActiveModel::Serializers::JSON

private

Expand Down
24 changes: 18 additions & 6 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
class User < ApplicationRecord
include HasGroups

ROLES = %w[admin regular api].freeze

has_secure_password validations: false

before_validation :downcase_email, on: [:create, :update]

validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: /\A[^@\s]+@([^@.\s]+\.)+[^@.\s]+\z/ }
validates :first_name, :last_name, presence: true
validates :password, length: { minimum: PASSWORD_MIN_LENGTH },
confirmation: true,
allow_nil: true
confirmation: true,
allow_nil: true
validates :role, inclusion: ROLES

scope :admins, -> { where(role: "admin") }
scope :regular, -> { where(role: "regular") }
scope :api, -> { where(role: "api") }

scope :admins, -> { where(admin: true) }
scope :regular, -> { where(admin: false) }
def admin?
role == "admin"
end

def user?
!admin?
role == "regular"
end

def api?
role == "api"
end

def full_name
Expand All @@ -29,6 +41,6 @@ def authenticate(given_password)
private

def downcase_email
self.email = self.email.downcase
self.email = email.downcase
end
end
Loading

0 comments on commit c99a7b3

Please sign in to comment.