Skip to content

Commit

Permalink
Merge pull request #5165 from coolo/attribute_events2
Browse files Browse the repository at this point in the history
More attribute refactoring
  • Loading branch information
coolo committed Jun 26, 2018
2 parents 2d29d73 + 368efa8 commit 397520c
Show file tree
Hide file tree
Showing 25 changed files with 460 additions and 405 deletions.
7 changes: 7 additions & 0 deletions ReleaseNotes-2.10
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ Intentional changes:
Rendered layouts/_google_analytics.html.erb (0.4ms)
Completed 200 OK in 79ms (Views: 78.8ms | ActiveRecord: 0.0ms)
```
* In previous releases it was possible to delete attributes through
/source/<project>/_attribute/?namespace=OBS&name=VeryImportantProject (or similiar
for packages). You need to follow the documentation now and the proper
route is /source/<project>/_attribute/OBS:VeryImportantProject

* GET '/attribute/:attribute' route responded with a 400 when the attribute type
did not exist. It now returns a 404 status.


Other changes
Expand Down
320 changes: 46 additions & 274 deletions src/api/app/controllers/attribute_controller.rb
Original file line number Diff line number Diff line change
@@ -1,301 +1,73 @@
class AttributeController < ApplicationController
include ValidationHelper

validate_action index: { method: :get, response: :directory }
validate_action namespace_definition: { method: :get, response: :attribute_namespace_meta }
validate_action namespace_definition: { method: :delete, response: :status }
validate_action namespace_definition: { method: :put, request: :attribute_namespace_meta, response: :status }
validate_action namespace_definition: { method: :post, request: :attribute_namespace_meta, response: :status }
validate_action attribute_definition: { method: :get, response: :attrib_type }
validate_action attribute_definition: { method: :delete, response: :status }
validate_action attribute_definition: { method: :put, request: :attrib_type, response: :status }
validate_action attribute_definition: { method: :post, request: :attrib_type, response: :status }

def index
if params[:namespace]
an = AttribNamespace.where(name: params[:namespace]).first
unless an
render_error status: 400, errorcode: 'unknown_namespace',
message: "Attribute namespace does not exist: #{params[:namespace]}"
return
end
list = an.attrib_types.pluck(:name)
validate_action show: { method: :get, response: :attrib_type }
validate_action delete: { method: :delete, response: :status }
validate_action update: { method: :put, request: :attrib_type, response: :status }
validate_action update: { method: :post, request: :attrib_type, response: :status }
before_action :load_attribute, only: [:show, :update, :delete]

# GET /attribute/:namespace/:name/_meta
def show
if @at
render template: 'attribute/show'
else
list = AttribNamespace.pluck(:name)
end

builder = Builder::XmlMarkup.new(indent: 2)
xml = builder.directory(count: list.length) do |dir|
list.each do |a|
dir.entry(name: a)
end
render_error message: "Unknown attribute '#{@namespace}':'#{@name}'",
status: 404, errorcode: 'unknown_attribute'
end

render xml: xml
end

# /attribute/:namespace/_meta
def namespace_definition
if params[:namespace].nil?
raise MissingParameterError, "parameter 'namespace' is missing"
end
namespace = params[:namespace]

if request.get?
@an = AttribNamespace.where(name: namespace).select(:id, :name).first
unless @an
render_error message: "Unknown attribute namespace '#{namespace}'",
status: 404, errorcode: 'unknown_attribute_namespace'
end
return
end

# namespace definitions must be managed by the admin
return unless extract_user
unless User.current.is_admin?
render_error status: 403, errorcode: 'permissions denied',
message: 'Namespace changes are only permitted by the administrator'
return
# DELETE /attribute/:namespace/:name/_meta
# DELETE /attribute/:namespace/:name
def delete
if @at
authorize @at, :destroy?
@at.destroy
end

if request.post? || request.put?
logger.debug '--- updating attribute namespace definitions ---'

xml_element = Xmlhash.parse(request.raw_post)

unless xml_element['name'] == namespace
render_error status: 400, errorcode: 'illegal_request',
message: "Illegal request: PUT/POST #{request.path}: path does not match content"
return
end

db = AttribNamespace.where(name: namespace).first
if db
logger.debug '* updating existing attribute namespace'
db.update_from_xml(xml_element)
else
logger.debug '* create new attribute namespace'
AttribNamespace.create(name: namespace).update_from_xml(xml_element)
end

logger.debug '--- finished updating attribute namespace definitions ---'
render_ok
elsif request.delete?
AttribNamespace.where(name: namespace).destroy_all
render_ok
else
render_error status: 400, errorcode: 'illegal_request',
message: "Illegal request: POST #{request.path}"
end
render_ok
end

# /attribute/:namespace/:name/_meta
def attribute_definition
if params[:namespace].nil?
raise MissingParameterError, "parameter 'namespace' is missing"
end
if params[:name].nil?
raise MissingParameterError, "parameter 'name' is missing"
end
namespace = params[:namespace]
name = params[:name]
ans = AttribNamespace.where(name: namespace).first
unless ans
render_error status: 400, errorcode: 'unknown_attribute_namespace',
message: "Specified attribute namespace does not exist: '#{namespace}'"
return
end

if request.get?
@at = ans.attrib_types.find_by(name: name)
unless @at
render_error message: "Unknown attribute '#{namespace}':'#{name}'",
status: 404, errorcode: 'unknown_attribute'
end
return
end

# permission check via User model
return unless extract_user

if request.post? || request.put?
logger.debug '--- updating attribute type definitions ---'

xml_element = Xmlhash.parse(request.raw_post)

unless xml_element && xml_element['name'] == name && xml_element['namespace'] == namespace
render_error status: 400, errorcode: 'illegal_request',
message: "Illegal request: PUT/POST #{request.path}: path does not match content"
return
end

entry = ans.attrib_types.where('name = ?', name).first

if entry
authorize entry, :update?

db = AttribType.find(entry.id) # get a writable object
logger.debug '* updating existing attribute definitions'
db.update_from_xml(xml_element)
else
entry = AttribType.new(name: name, attrib_namespace: ans)
authorize entry, :create?

logger.debug '* create new attribute definition'
entry.update_from_xml(xml_element)
end

logger.debug '--- finished updating attribute namespace definitions ---'
#--- end update attribute namespace definitions ---#

render_ok
elsif request.delete?
at = ans.attrib_types.where('name = ?', name).first
# POST/PUT /attribute/:namespace/:name/_meta
def update
return unless (xml_element = validate_xml)

if at
authorize at, :destroy?
at.destroy
end

render_ok
if @at
authorize entry, :update?
@at.update_from_xml(xml_element)
else
render_error status: 400, errorcode: 'illegal_request',
message: "Illegal request: POST #{request.path}"
end
end

class RemoteProject < APIException
setup 400, 'Attribute access to remote project is not yet supported'
end

class InvalidAttribute < APIException
end

# GET
# /source/:project/_attribute/:attribute
# /source/:project/:package/_attribute/:attribute
# /source/:project/:package/:binary/_attribute/:attribute
#--------------------------------------------------------
def show_attribute
find_attribute_container

# init
# checks
# exec
if params[:rev] || params[:meta] || params[:view] || @attribute_container.nil?
# old or remote instance entry
render xml: Backend::Api::Sources::Package.attributes(params[:project], params[:package], params)
return
end

render xml: @attribute_container.render_attribute_axml(params)
end

# DELETE
# /source/:project/_attribute/:attribute
# /source/:project/:package/_attribute/:attribute
# /source/:project/:package/:binary/_attribute/:attribute
#--------------------------------------------------------
def delete_attribute
find_attribute_container

# init
if params[:namespace].blank? || params[:name].blank?
render_error status: 400, errorcode: 'missing_attribute',
message: 'No attribute got specified for delete'
return
end
ac = @attribute_container.find_attribute(params[:namespace], params[:name], @binary)

# checks
unless ac
render_error(status: 404, errorcode: 'not_found',
message: "Attribute #{params[:attribute]} does not exist") && return
end
unless User.current.can_create_attribute_in? @attribute_container, namespace: params[:namespace], name: params[:name]
render_error status: 403, errorcode: 'change_attribute_no_permission',
message: "user #{user.login} has no permission to change attribute"
return
create(xml_element)
end

# exec
ac.destroy
@attribute_container.write_attributes(params[:comment])
render_ok
end

# POST
# /source/:project/_attribute/:attribute
# /source/:project/:package/_attribute/:attribute
# /source/:project/:package/:binary/_attribute/:attribute
#--------------------------------------------------------
def cmd_attribute
find_attribute_container

# init
req = ActiveXML::Node.new(request.raw_post)

# This is necessary for checking the authorization and do not create the attribute
# The attribute creation will happen in @attribute_container.store_attribute_axml
req.each('attribute') do |attr|
attrib_type = AttribType.find_by_namespace_and_name!(attr.value('namespace'), attr.value('name'))
attrib = Attrib.new(attrib_type: attrib_type)

attr.each('value') do |value|
attrib.values.new(value: value.text)
end

attrib.container = @attribute_container

unless attrib.valid?
raise APIException, message: attrib.errors.full_messages.join('\n'), status: 400
end

authorize attrib, :create?
end
private

# exec
changed = false
req.each('attribute') do |attr|
changed = true if @attribute_container.store_attribute_axml(attr, @binary)
def load_attribute
@namespace = params[:namespace]
@ans = AttribNamespace.find_by_name!(@namespace)
if params[:name].nil?
raise MissingParameterError, "parameter 'name' is missing"
end
logger.debug "Attributes for #{@attribute_container.class} #{@attribute_container.name} changed, writing to backend" if changed
@attribute_container.write_attributes(params[:comment]) if changed
render_ok
@name = params[:name]
# find_by_name is something else (of course)
@at = @ans.attrib_types.where(name: @name).first
end

protected

before_action :require_valid_project_name, only: [:find_attribute_container]
def create(xml_element)
entry = AttribType.new(name: @name, attrib_namespace: @ans)
authorize entry, :create?

def find_attribute_container
# init and validation
#--------------------
params[:user] = User.current.login if User.current
@binary = nil
@binary = params[:binary] if params[:binary]
# valid post commands
if params[:package] && params[:package] != '_project'
@attribute_container = Package.get_by_project_and_name(params[:project], params[:package], use_source: false)
else
# project
raise RemoteProject if Project.is_remote_project?(params[:project])
@attribute_container = Project.get_by_name(params[:project])
end
entry.update_from_xml(xml_element)
end

# is the attribute type defined at all ?
return if params[:attribute].blank?
def validate_xml
xml_element = Xmlhash.parse(request.raw_post)

# Valid attribute
aname = params[:attribute]
name_parts = aname.split(/:/)
if name_parts.length != 2
raise InvalidAttribute, "attribute '#{aname}' must be in the $NAMESPACE:$NAME style"
end
# existing ?
AttribType.find_by_name!(params[:attribute])
# only needed for a get request
params[:namespace] = name_parts[0]
params[:name] = name_parts[1]
return xml_element if xml_element && xml_element['name'] == @name && xml_element['namespace'] == @namespace
render_error status: 400, errorcode: 'illegal_request',
message: "Illegal request: PUT/POST #{request.path}: path does not match content"
return
end
end
Loading

0 comments on commit 397520c

Please sign in to comment.