Skip to content

Commit

Permalink
Fixes #15068 - Provide a way to select SSL certs for a product
Browse files Browse the repository at this point in the history
  • Loading branch information
sbernhard authored and jlsherrill committed Feb 7, 2018
1 parent 6c6d2c8 commit 95594cf
Show file tree
Hide file tree
Showing 56 changed files with 983 additions and 497 deletions.
115 changes: 115 additions & 0 deletions app/controllers/katello/api/v2/content_credentials_controller.rb
@@ -0,0 +1,115 @@
module Katello
class Api::V2::ContentCredentialsController < Api::V2::ApiController
include Katello::Concerns::FilteredAutoCompleteSearch
before_action :authorize
before_action :find_organization, :only => [:create, :index, :auto_complete_search]
before_action :find_content_credential, :only => [:show, :update, :destroy, :content, :set_content]
skip_before_action :check_content_type, :only => [:create, :content, :set_content]

def resource_class
Katello::GpgKey
end

def_param_group :content_credential do
param :name, :identifier, :action_aware => true, :required => true, :desc => N_("identifier of the content credential")
param :content_type, String, :action_aware => true, :required => true, :desc => N_("type of content")
param :content, String, :action_aware => true, :required => true, :desc => N_("public key block in DER encoding / certificate content")
end

resource_description do
description <<-DESC
# Description
Content Credentials are used to store credentials like GPG Keys and Certificates for the authentication
to Products / Repositories.
DESC
api_version "v2"
end

api :GET, "/content_credentials", N_("List content credentials")
param :organization_id, :number, :desc => N_("organization identifier"), :required => true
param :name, String, :desc => N_("name of the Content Credential"), :required => false
param :content_type, String, :desc => N_("type of content"), :required => false
param_group :search, Api::V2::ApiController
def index
respond(:collection => scoped_search(index_relation.uniq, :name, :asc))
end

def index_relation
query = GpgKey.readable.where(:organization_id => @organization.id)
query = query.where(:name => params[:name]) if params[:name]
query
end

api :POST, "/content_credentials", N_("Create a content credential")
param :organization_id, :number, :desc => N_("organization identifier"), :required => true
param_group :content_credential, :as => :create
def create
filepath = params.try(:[], :file_path).try(:path)

content = nil
if filepath
content = File.open(filepath, "rb") { |file| file.read }
else
content = params[:content]
end

content_credential = @organization.gpg_keys.create!(content_credential_params.merge(:content => content))
respond_for_show(:resource => content_credential)
end

api :GET, "/content_credentials/:id", N_("Show a content credential")
param :id, :number, :desc => N_("content credential numeric identifier"), :required => true
def show
respond_for_show(:resource => @content_credential)
end

api :PUT, "/content_credentials/:id", N_("Update a content credential")
param :id, :number, :desc => N_("content credential numeric identifier"), :required => true
param_group :content_credential
def update
@content_credential.update_attributes!(content_credential_params)
respond_for_show(:resource => @content_credential)
end

api :DELETE, "/content_credentials/:id", N_("Destroy a content credential")
param :id, :number, :desc => N_("content credential numeric identifier"), :required => true
def destroy
@content_credential.destroy
respond_for_destroy
end

api :GET, "/content_credentials/:id/content", N_("Return the content of a content credential, used directly by yum")
param :id, :number, :required => true
def content
render(:plain => @content_credential.content, :layout => false)
end

api :POST, "/content_credentials/:id/content", N_("Upload content credential contents")
param :id, :number, :desc => N_("content credential numeric identifier"), :required => true
param :content, File, :desc => N_("file contents"), :required => true
def set_content
filepath = params.try(:[], :content).try(:path)

if filepath
content = File.open(filepath, "rb") { |file| file.read }
@content_credential.update_attributes!(:content => content)
render :json => {:status => "success"}
else
fail HttpErrors::BadRequest, _("No file uploaded")
end
end

protected

def find_content_credential
@content_credential = GpgKey.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise HttpErrors::NotFound, _("Couldn't find Content Credential '%s'") % params[:id]
end

def content_credential_params
params.permit(:name, :content_type, :content)
end
end
end
5 changes: 5 additions & 0 deletions app/controllers/katello/api/v2/gpg_keys_controller.rb
@@ -1,6 +1,7 @@
module Katello
class Api::V2::GpgKeysController < Api::V2::ApiController
include Katello::Concerns::FilteredAutoCompleteSearch
before_action :deprecated
before_action :authorize
before_action :find_organization, :only => [:create, :index, :auto_complete_search]
before_action :find_gpg_key, :only => [:show, :update, :destroy, :content, :set_content]
Expand Down Expand Up @@ -103,5 +104,9 @@ def find_gpg_key
def gpg_key_params
params.permit(:name, :content)
end

def deprecated
::Foreman::Deprecation.api_deprecation_warning("it will be removed in Katello 4.0, Please see /api/v2/content_credentials")
end
end
end
32 changes: 31 additions & 1 deletion app/controllers/katello/api/v2/products_controller.rb
Expand Up @@ -7,6 +7,9 @@ class Api::V2::ProductsController < Api::V2::ApiController
before_action :find_product, :only => [:update, :destroy, :sync]
before_action :find_organization_from_product, :only => [:update]
before_action :authorize_gpg_key, :only => [:update, :create]
before_action :authorize_ssl_ca_cert, :only => [:update, :create]
before_action :authorize_ssl_client_cert, :only => [:update, :create]
before_action :authorize_ssl_client_key, :only => [:update, :create]

resource_description do
api_version "v2"
Expand All @@ -15,6 +18,9 @@ class Api::V2::ProductsController < Api::V2::ApiController
def_param_group :product do
param :description, String, :desc => N_("Product description")
param :gpg_key_id, :number, :desc => N_("Identifier of the GPG key")
param :ssl_ca_cert_id, :number, :desc => N_("Idenifier of the SSL CA Cert")
param :ssl_client_cert_id, :number, :desc => N_("Identifier of the SSL Client Cert")
param :ssl_client_key_id, :number, :desc => N_("Identifier of the SSL Client Key")
param :sync_plan_id, :number, :desc => N_("Plan numeric identifier"), :allow_nil => true
end

Expand Down Expand Up @@ -145,12 +151,36 @@ def authorize_gpg_key
end
end

def authorize_ssl_ca_cert
ssl_ca_cert_id = product_params[:ssl_ca_cert_id]
if ssl_ca_cert_id
ssl_ca_cert = GpgKey.readable.where(:id => ssl_ca_cert_id, :organization_id => @organization).first
fail HttpErrors::NotFound, _("Couldn't find ssl ca cert '%s'") % ssl_ca_cert_id if ssl_ca_cert.nil?
end
end

def authorize_ssl_client_cert
ssl_client_cert_id = product_params[:ssl_client_cert_id]
if ssl_client_cert_id
ssl_client_cert = GpgKey.readable.where(:id => ssl_client_cert_id, :organization_id => @organization).first
fail HttpErrors::NotFound, _("Couldn't find ssl client cert '%s'") % ssl_client_cert_id if ssl_client_cert.nil?
end
end

def authorize_ssl_client_key
ssl_client_key_id = product_params[:ssl_client_key_id]
if ssl_client_key_id
ssl_client_key = GpgKey.readable.where(:id => ssl_client_key_id, :organization_id => @organization).first
fail HttpErrors::NotFound, _("Couldn't find ssl client key '%s'") % ssl_client_key_id if ssl_client_key.nil?
end
end

def product_params
# only allow sync plan id to be updated if the product is a Red Hat product
if @product && @product.redhat?
params.require(:product).permit(:sync_plan_id)
else
params.require(:product).permit(:name, :label, :description, :provider_id, :gpg_key_id, :sync_plan_id)
params.require(:product).permit(:name, :label, :description, :provider_id, :gpg_key_id, :ssl_ca_cert_id, :ssl_client_cert_id, :ssl_client_key_id, :sync_plan_id)
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions app/lib/katello/validators/gpg_key_content_type_validator.rb
@@ -0,0 +1,22 @@
module Katello
module Validators
class GpgKeyContentTypeValidator < ActiveModel::Validator
def validate(record)
# need to verify, that gpg_key is of GpgKey.content type "gpg_key" and
# ssl_ca_cert, ssl_client_cert, ssl_client_key of GpgKey.content type "cert"

if !record.gpg_key.blank? && record.gpg_key.content_type != "gpg_key"
record.errors[:gpg_key] << _("Wrong content type submitted.")
end

if record.instance_of?(Katello::Product)
[:ssl_ca_cert, :ssl_client_cert, :ssl_client_key].each do |cert|
if !record.send(cert).blank? && record.send(cert).content_type != "cert"
record.errors[cert] << _("Wrong content type submitted.")
end
end
end
end
end
end
end
6 changes: 6 additions & 0 deletions app/models/katello/glue/pulp/repo.rb
Expand Up @@ -255,6 +255,12 @@ def importer_connection_options(capsule = SmartProxy.default_capsule!)
:ssl_ca_cert => Katello::Repository.feed_ca_cert(url),
:proxy_host => self.proxy_host_value
}
elsif self.product.ssl_client_cert && self.product.ssl_client_key && self.product.ssl_ca_cert
importer_options = {
:ssl_client_cert => self.product.ssl_client_cert.content,
:ssl_client_key => self.product.ssl_client_key.content,
:ssl_ca_cert => self.product.ssl_ca_cert.content
}
else
importer_options = {
:ssl_client_cert => nil,
Expand Down
15 changes: 13 additions & 2 deletions app/models/katello/gpg_key.rb
Expand Up @@ -6,21 +6,32 @@ class GpgKey < Katello::Model

has_many :repositories, :class_name => "Katello::Repository", :inverse_of => :gpg_key, :dependent => :nullify
has_many :products, :class_name => "Katello::Product", :inverse_of => :gpg_key, :dependent => :nullify

has_many :ssl_ca_products, :class_name => "Katello::Product", :foreign_key => "ssl_ca_cert_id",
:inverse_of => :ssl_ca_cert, :dependent => :nullify
has_many :ssl_client_products, :class_name => "Katello::Product", :foreign_key => "ssl_client_cert_id",
:inverse_of => :ssl_client_cert, :dependent => :nullify
has_many :ssl_key_products, :class_name => "Katello::Product", :foreign_key => "ssl_client_key_id",
:inverse_of => :ssl_client_key, :dependent => :nullify
belongs_to :organization, :inverse_of => :gpg_keys

validates_lengths_from_database
validates :name, :presence => true, :uniqueness => {:scope => :organization_id,
:message => N_("has already been taken")}
validates :content_type, :presence => true, :inclusion => { :in => %w(gpg_key cert),
:message => N_("must be gpg_key or cert")}
validates :content, :presence => true, :length => {:maximum => MAX_CONTENT_LENGTH}
validates :organization, :presence => true
validates_with Validators::KatelloNameFormatValidator, :attributes => :name
validates_with Validators::ContentValidator, :attributes => :content
validates_with Validators::GpgKeyContentValidator, :attributes => :content, :if => proc { SETTINGS[:katello][:gpg_strict_validation] }
validates_with Validators::GpgKeyContentValidator, :attributes => :content, :if => :use_gpg_content_validator?

scoped_search :on => :name, :complete_value => true
scoped_search :on => :organization_id, :complete_value => true, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER

def use_gpg_content_validator?
content_type == "gpg_key" && SETTINGS[:katello][:gpg_strict_validation]
end

def as_json(options = {})
options ||= {}
ret = super(options.except(:details))
Expand Down
4 changes: 4 additions & 0 deletions app/models/katello/product.rb
Expand Up @@ -15,6 +15,9 @@ class Product < Katello::Model
belongs_to :gpg_key, :inverse_of => :products
has_many :product_contents, :foreign_key => 'product_id', :class_name => "Katello::ProductContent", :dependent => :destroy
has_many :displayable_product_contents, -> { displayable }, :foreign_key => 'product_id', :class_name => "Katello::ProductContent", :dependent => :destroy
belongs_to :ssl_ca_cert, :class_name => "Katello::GpgKey", :inverse_of => :ssl_ca_products
belongs_to :ssl_client_cert, :class_name => "Katello::GpgKey", :inverse_of => :ssl_client_products
belongs_to :ssl_client_key, :class_name => "Katello::GpgKey", :inverse_of => :ssl_key_products
has_many :repositories, :class_name => "Katello::Repository", :dependent => :restrict_with_exception

has_many :subscription_products, :class_name => "Katello::SubscriptionProduct", :dependent => :destroy
Expand All @@ -27,6 +30,7 @@ class Product < Katello::Model
validates_with Validators::KatelloLabelFormatValidator, :attributes => :label
validates_with Validators::ProductUniqueAttributeValidator, :attributes => :name
validates_with Validators::ProductUniqueAttributeValidator, :attributes => :label
validates_with Validators::GpgKeyContentTypeValidator

scoped_search :on => :name, :complete_value => true
scoped_search :on => :organization_id, :complete_value => true, :only_explicit => true, :validator => ScopedSearch::Validators::INTEGER
Expand Down
3 changes: 3 additions & 0 deletions app/views/katello/api/v2/content_credentials/index.json.rabl
@@ -0,0 +1,3 @@
object false

extends "katello/api/v2/common/index"
70 changes: 70 additions & 0 deletions app/views/katello/api/v2/content_credentials/show.json.rabl
@@ -0,0 +1,70 @@
object @resource

extends 'katello/api/v2/common/identifier'
extends 'katello/api/v2/common/org_reference'
extends 'katello/api/v2/common/timestamps'

attributes :name
attributes :content_type
attributes :content

child :products => :products do
attributes :id, :cp_id, :name
node :repository_count do |product|
product.repositories.count
end
child :provider => :provider do
attribute :name
attribute :id
end
end

child :repositories => :repositories do
attribute :id
attribute :name

child :product do |_product|
attributes :id, :cp_id, :name
end
end

child :ssl_ca_products => :ssl_ca_products do
attributes :id, :cp_id, :name
node :repository_count do |product|
product.repositories.count
end
child :provider => :provider do
attribute :name
attribute :id
end
end

child :ssl_client_products => :ssl_client_products do
attributes :id, :cp_id, :name
node :repository_count do |product|
product.repositories.count
end
child :provider => :provider do
attribute :name
attribute :id
end
end

child :ssl_key_products => :ssl_key_products do
attributes :id, :cp_id, :name
node :repository_count do |product|
product.repositories.count
end
child :provider => :provider do
attribute :name
attribute :id
end
end

node :permissions do |content_credential|
{
:view_content_credenials => content_credential.readable?,
:edit_content_credenials => content_credential.editable?,
:destroy_content_credenials => content_credential.deletable?
}
end
3 changes: 3 additions & 0 deletions app/views/katello/api/v2/products/base.json.rabl
Expand Up @@ -9,6 +9,9 @@ attributes :provider_id
attributes :sync_plan_id
attributes :sync_summary
attributes :gpg_key_id
attributes :ssl_ca_cert_id
attributes :ssl_client_cert_id
attributes :ssl_client_key_id

child({:available_content => :available_content}, :if => params[:include_available_content]) do
extends "katello/api/v2/products/product_content"
Expand Down
12 changes: 12 additions & 0 deletions app/views/katello/api/v2/products/show.json.rabl
Expand Up @@ -21,6 +21,18 @@ node(:gpg_key, :unless => lambda { |product| product.gpg_key.nil? }) do |product
{:id => product.gpg_key.id, :name => product.gpg_key.name}
end

node(:ssl_ca_cert, :unless => lambda { |product| product.ssl_ca_cert.nil? }) do |product|
{:id => product.ssl_ca_cert.id, :name => product.ssl_ca_cert.name}
end

node(:ssl_client_cert, :unless => lambda { |product| product.ssl_client_cert.nil? }) do |product|
{:id => product.ssl_client_cert.id, :name => product.ssl_client_cert.name}
end

node(:ssl_client_key, :unless => lambda { |product| product.ssl_client_key.nil? }) do |product|
{:id => product.ssl_client_key.id, :name => product.ssl_client_key.name}
end

child :provider do
attribute :name
end
Expand Down
8 changes: 8 additions & 0 deletions config/routes/api/v2.rb
Expand Up @@ -54,6 +54,14 @@ class ActionDispatch::Routing::Mapper
end
end

api_resources :content_credentials, :only => [:index, :show, :create, :update, :destroy] do
member do
get :content
post :content, :action => :set_content
end
get :auto_complete_search, :on => :collection
end

match '/content_views/:composite_content_view_id/content_view_components' => 'content_view_components#index', :via => :get
match '/content_views/:composite_content_view_id/content_view_components/:id' => 'content_view_components#show', :via => :get
match '/content_views/:composite_content_view_id/content_view_components/add' => 'content_view_components#add_components', :via => :put
Expand Down

0 comments on commit 95594cf

Please sign in to comment.