user module

This commit is contained in:
amartynov 2014-12-12 17:00:06 +03:00
parent 98fcbca3b5
commit cbf48145cd
6 changed files with 461 additions and 294 deletions

View File

@ -13,13 +13,22 @@ require "db/mongo/mongo_connector"
require "providers/provider_factory"
require "routes/v2.0"
require "helpers/version_2"
require "routes/v2.0/provider"
require "routes/v2.0/user"
class DevopsService < Sinatra::Base
helpers Sinatra::Streaming
helpers Sinatra::Version2_0::Helpers
register Sinatra::Version2_0::Core::ProviderRoutes
register Sinatra::Version2_0::Core::UserRoutes
def initialize config
super()
self.class.set :config, config
@@config = config
root = File.dirname(__FILE__)
@@config[:keys_dir] = File.join(root, "../.devops_files/keys")
@ -29,9 +38,10 @@ class DevopsService < Sinatra::Base
end
[:keys_dir, :scripts_dir].each {|key| d = @@config[key]; FileUtils.mkdir_p(d) unless File.exists?(d) }
mongo = DevopsService.mongo
self.class.set :mongo, mongo
mongo.create_root_user
::Provider::ProviderFactory.init(config)
set_up_providers_keys!(::Provider::ProviderFactory.all, mongo)
#set_up_providers_keys!(::Provider::ProviderFactory.all, mongo)
end
@@mongo
@ -54,7 +64,92 @@ class DevopsService < Sinatra::Base
end
end
use ::Version2_0::V2_0
include Sinatra::JSON
configure :production do
disable :dump_errors
disable :show_exceptions
set :logging, Logger::INFO
end
configure :development do
set :logging, Logger::DEBUG
disable :raise_errors
# disable :dump_errors
set :show_exceptions, :after_handler
end
not_found do
"Not found"
end
error RecordNotFound do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 404)
end
error InvalidRecord do
e = env["sinatra.error"]
logger.warn e.message
logger.warn "Request body: #{request.body.read}"
halt_response(e.message, 400)
end
error InvalidCommand do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 400)
end
error DependencyError do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 400)
end
error InvalidPrivileges do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 401)
end
error Excon::Errors::Unauthorized do
e = env["sinatra.error"]
resp = e.response
ct = resp.headers["Content-Type"]
msg = unless ct.nil?
if ct.include?("application/json")
json = ::Chef::JSONCompat.from_json(resp.body)
m = "ERROR: Unauthorized (#{json['error']['code']}): #{json['error']['message']}"
logger.error(m)
else
end
m
else
"Unauthorized: #{e.inspect}"
end
halt_response(msg, 500)
end
error Fog::Compute::AWS::Error do
e = env["sinatra.error"]
logger.error e.message
halt_response(e.message, 500)
end
error do
e = env["sinatra.error"]
logger.error e.message
halt_response(e.message, 500)
end
# def self.mongo
# DevopsService.mongo
# end
# use ::Version2_0::V2_0
private

View File

@ -0,0 +1,47 @@
require 'sinatra/base'
require "sinatra/json"
require "providers/provider_factory"
module Sinatra
module Version2_0
module Request
module Helpers
# Check request headers
def check_headers *headers
ha = (headers.empty? ? [:accept, :content_type] : headers)
ha.each do |h|
case h
when :accept, "accept"
accept_json
when :content_type, "content_type"
request_json
end
end
end
# Check Accept header
#
# Can client works with JSON?
def accept_json
logger.debug(request.accept)
unless request.accept? 'application/json'
response.headers['Accept'] = 'application/json'
halt_response("Accept header should contains 'application/json' type", 406)
end
rescue NoMethodError => e
#error in sinatra 1.4.4 (https://github.com/sinatra/sinatra/issues/844, https://github.com/sinatra/sinatra/pull/805)
response.headers['Accept'] = 'application/json'
halt_response("Accept header should contains 'application/json' type", 406)
end
# Check Content-Type header
def request_json
halt_response("Content-Type should be 'application/json'", 415) if request.media_type.nil? or request.media_type != 'application/json'
end
end
end
end
end

View File

@ -0,0 +1,131 @@
require "json"
require 'sinatra/base'
require "sinatra/json"
require "providers/provider_factory"
module Sinatra
module Version2_0
module Helpers
def create_response msg, obj=nil, rstatus=200
logger.info(msg)
status rstatus
obj = {} if obj.nil?
obj[:message] = msg
json(obj)
end
def halt_response msg, rstatus=400
obj = {:message => msg}
halt(rstatus, json(obj))
end
def check_privileges cmd, p
#BaseRoutes.mongo.check_user_privileges(request.env['REMOTE_USER'], cmd, p)
true
end
# Check request headers
def check_headers *headers
ha = (headers.empty? ? [:accept, :content_type] : headers)
ha.each do |h|
case h
when :accept, "accept"
accept_json
when :content_type, "content_type"
request_json
end
end
end
# Check Accept header
#
# Can client works with JSON?
def accept_json
logger.debug(request.accept)
unless request.accept? 'application/json'
response.headers['Accept'] = 'application/json'
halt_response("Accept header should contains 'application/json' type", 406)
end
rescue NoMethodError => e
#error in sinatra 1.4.4 (https://github.com/sinatra/sinatra/issues/844, https://github.com/sinatra/sinatra/pull/805)
response.headers['Accept'] = 'application/json'
halt_response("Accept header should contains 'application/json' type", 406)
end
# Check Content-Type header
def request_json
halt_response("Content-Type should be 'application/json'", 415) if request.media_type.nil? or request.media_type != 'application/json'
end
def check_provider provider
list = ::Provider::ProviderFactory.providers
halt_response("Invalid provider '#{provider}', available providers: '#{list.join("', '")}'", 404) unless list.include?(provider)
end
def create_object_from_json_body type=Hash, empty_body=false
json = request.body.read.strip
return nil if json.empty? and empty_body
@body_json = begin
::JSON.parse(json)
rescue ::JSON::ParserError => e
logger.error e.message
halt_response("Invalid JSON: #{e.message}")
end
halt_response("Invalid JSON, it should be an #{type == Array ? "array" : "object"}") unless @body_json.is_a?(type)
@body_json
end
def check_string val, msg, _nil=false, empty=false
check_param val, String, msg, _nil, empty
end
def check_array val, msg, vals_type=String, _nil=false, empty=false
check_param val, Array, msg, _nil, empty
val.each {|v| halt_response(msg) unless v.is_a?(vals_type)} unless val.nil?
val
end
def check_filename file_name, not_string_msg, json_resp=true
check_string file_name, not_string_msg
r = Regexp.new("^[\\w _\\-.]{1,255}$", Regexp::IGNORECASE)
if r.match(file_name).nil?
msg = "Invalid file name '#{file_name}'. Expected name with 'a'-'z', '0'-'9', ' ', '_', '-', '.' symbols with length greate then 0 and less then 256 "
if json_resp
halt_response(msg)
else
halt(400, msg)
end
end
file_name
end
def check_param val, type, msg, _nil=false, empty=false
if val.nil?
if _nil
return val
else
halt_response(msg)
end
end
if val.is_a?(type)
halt_response(msg) if val.empty? and !empty
val
else
halt_response(msg)
end
end
# Save information about requests with methods POST, PUT, DELETE
def statistic msg=nil
unless request.get?
settings.mongo.statistic request.env['REMOTE_USER'], request.path, request.request_method, @body_json, response.status
end
end
end
end
end

View File

@ -31,38 +31,6 @@ module Version2_0
BaseRoutes.mongo.check_user_privileges(request.env['REMOTE_USER'], cmd, p)
end
# Check request headers
def check_headers *headers
ha = (headers.empty? ? [:accept, :content_type] : headers)
ha.each do |h|
case h
when :accept, "accept"
accept_json
when :content_type, "content_type"
request_json
end
end
end
# Check Accept header
#
# Can client works with JSON?
def accept_json
logger.debug(request.accept)
unless request.accept? 'application/json'
response.headers['Accept'] = 'application/json'
halt_response("Accept header should contains 'application/json' type", 406)
end
rescue NoMethodError => e
#error in sinatra 1.4.4 (https://github.com/sinatra/sinatra/issues/844, https://github.com/sinatra/sinatra/pull/805)
response.headers['Accept'] = 'application/json'
halt_response("Accept header should contains 'application/json' type", 406)
end
# Check Content-Type header
def request_json
halt_response("Content-Type should be 'application/json'", 415) if request.media_type.nil? or request.media_type != 'application/json'
end
def check_provider provider
list = ::Provider::ProviderFactory.providers
@ -132,89 +100,6 @@ module Version2_0
end
include Sinatra::JSON
configure :production do
disable :dump_errors
disable :show_exceptions
set :logging, Logger::INFO
end
configure :development do
set :logging, Logger::DEBUG
disable :raise_errors
# disable :dump_errors
set :show_exceptions, :after_handler
end
not_found do
"Not found"
end
error RecordNotFound do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 404)
end
error InvalidRecord do
e = env["sinatra.error"]
logger.warn e.message
logger.warn "Request body: #{request.body.read}"
halt_response(e.message, 400)
end
error InvalidCommand do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 400)
end
error DependencyError do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 400)
end
error InvalidPrivileges do
e = env["sinatra.error"]
logger.warn e.message
halt_response(e.message, 401)
end
error Excon::Errors::Unauthorized do
e = env["sinatra.error"]
resp = e.response
ct = resp.headers["Content-Type"]
msg = unless ct.nil?
if ct.include?("application/json")
json = ::Chef::JSONCompat.from_json(resp.body)
m = "ERROR: Unauthorized (#{json['error']['code']}): #{json['error']['message']}"
logger.error(m)
else
end
m
else
"Unauthorized: #{e.inspect}"
end
halt_response(msg, 500)
end
error Fog::Compute::AWS::Error do
e = env["sinatra.error"]
logger.error e.message
halt_response(e.message, 500)
end
error do
e = env["sinatra.error"]
logger.error e.message
halt_response(e.message, 500)
end
def self.mongo
DevopsService.mongo
end
end
end

View File

@ -1,33 +1,39 @@
# encoding: UTF-8
require 'sinatra/base'
require "json"
require "routes/v2.0/base_routes"
require "providers/provider_factory"
module Version2_0
class ProviderRoutes < BaseRoutes
module Sinatra
module Version2_0
module Core
module ProviderRoutes
def initialize wrapper
super wrapper
puts "Provider routes initialized"
def self.registered(app)
puts "Provider routes initialized"
# Get devops providers
#
# * *Request*
# - method : GET
# - headers :
# - Accept: application/json
#
# * *Returns* :
# [
# "ec2",
# "openstack"
# ]
app.get "/providers" do
check_headers :accept
check_privileges("provider", "r")
json ::Provider::ProviderFactory.providers
end
end
end
end
# Get devops providers
#
# * *Request*
# - method : GET
# - headers :
# - Accept: application/json
#
# * *Returns* :
# [
# "ec2",
# "openstack"
# ]
get "/providers" do
check_headers :accept
check_privileges("provider", "r")
json ::Provider::ProviderFactory.providers
end
end
#register Version2_0::Core::ProviderRoutes
end

View File

@ -1,167 +1,170 @@
require "json"
require "db/exceptions/invalid_record"
require "db/mongo/models/user"
module Version2_0
class UserRoutes < BaseRoutes
module Sinatra
module Version2_0
module Core
module UserRoutes
def initialize wrapper
super wrapper
puts "User routes initialized"
end
def self.registered(app)
puts "User routes initialized"
after %r{\A/user(/[\w]+(/password)?)?\z} do
statistic
end
app.after %r{\A/user(/[\w]+(/password)?)?\z} do
statistic
end
# Get users list
#
# * *Request*
# - method : GET
# - headers :
# - Accept: application/json
#
# * *Returns* :
# [
# {
# "email": "test@test.test",
# "privileges": {
# "flavor": "r",
# "group": "r",
# "image": "r",
# "project": "r",
# "server": "r",
# "key": "r",
# "user": "",
# "filter": "r",
# "network": "r",
# "provider": "r",
# "script": "r",
# "templates": "r"
# },
# "id": "test"
# }
# ]
get "/users" do
check_headers :accept
check_privileges("user", "r")
users = BaseRoutes.mongo.users.map {|i| i.to_hash}
users.each {|u| u.delete("password")}
json users
end
# Get users list
#
# * *Request*
# - method : GET
# - headers :
# - Accept: application/json
#
# * *Returns* :
# [
# {
# "email": "test@test.test",
# "privileges": {
# "flavor": "r",
# "group": "r",
# "image": "r",
# "project": "r",
# "server": "r",
# "key": "r",
# "user": "",
# "filter": "r",
# "network": "r",
# "provider": "r",
# "script": "r",
# "templates": "r"
# },
# "id": "test"
# }
# ]
app.get "/users" do
check_headers :accept
check_privileges("user", "r")
users = settings.mongo.users.map {|i| i.to_hash}
users.each {|u| u.delete("password")}
json users
end
# Create user
#
# * *Request*
# - method : POST
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "username": "user name",
# "email": "user email",
# "password": "user password"
# }
#
# * *Returns* :
# 201 - Created
post "/user" do
check_headers :accept, :content_type
check_privileges("user", "w")
user = create_object_from_json_body
["username", "password", "email"].each do |p|
check_string(user[p], "Parameter '#{p}' must be a not empty string")
end
BaseRoutes.mongo.user_insert User.new(user)
create_response("Created", nil, 201)
end
# Create user
#
# * *Request*
# - method : POST
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "username": "user name",
# "email": "user email",
# "password": "user password"
# }
#
# * *Returns* :
# 201 - Created
app.post "/user" do
check_headers :accept, :content_type
check_privileges("user", "w")
user = create_object_from_json_body
["username", "password", "email"].each do |p|
check_string(user[p], "Parameter '#{p}' must be a not empty string")
end
#BaseRoutes.mongo.user_insert User.new(user)
settings.mongo.user_insert User.new(user)
create_response("Created", nil, 201)
end
# Delete user
#
# * *Request*
# - method : DELETE
# - headers :
# - Accept: application/json
#
# * *Returns* :
# 200 - Deleted
delete "/user/:user" do
check_headers :accept
check_privileges("user", "w")
projects = BaseRoutes.mongo.projects_by_user params[:user]
if !projects.empty?
str = ""
projects.each do |p|
p.deploy_envs.each do |e|
str+="#{p.id}.#{e.identifier} " if e.users.include? params[:user]
# Delete user
#
# * *Request*
# - method : DELETE
# - headers :
# - Accept: application/json
#
# * *Returns* :
# 200 - Deleted
app.delete "/user/:user" do
check_headers :accept
check_privileges("user", "w")
projects = settings.mongo.projects_by_user params[:user]
if !projects.empty?
str = ""
projects.each do |p|
p.deploy_envs.each do |e|
str+="#{p.id}.#{e.identifier} " if e.users.include? params[:user]
end
end
logger.info projects
raise DependencyError.new "Deleting is forbidden: User is included in #{str}"
#return [400, "Deleting is forbidden: User is included in #{str}"]
end
r = settings.mongo.user_delete params[:user]
create_response("User '#{params[:user]}' removed")
end
# Change user privileges
#
# * *Request*
# - method : PUT
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "cmd": "command or all", -> if empty, set default privileges
# "privileges": "priv" -> 'rwx' or ''
# }
#
# * *Returns* :
# 200 - Updated
app.put "/user/:user" do
check_headers :accept, :content_type
check_privileges("user", "w")
data = create_object_from_json_body
user = settings.mongo.user params[:user]
cmd = check_string(data["cmd"], "Parameter 'cmd' should be a not empty string", true) || ""
privileges = check_string(data["privileges"], "Parameter 'privileges' should be a not empty string", true) || ""
user.grant(cmd, privileges)
settings.mongo.user_update user
create_response("Updated")
end
# Change user email/password
#
# * *Request*
# - method : PUT
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "email/password": "new user email/password",
# }
#
# * *Returns* :
# 200 - Updated
app.put %r{\A/user/[\w]+/(email|password)\z} do
check_headers :accept, :content_type
action = File.basename(request.path)
u = File.basename(File.dirname(request.path))
raise InvalidPrivileges.new("Access denied for '#{request.env['REMOTE_USER']}'") if u == User::ROOT_USER_NAME and request.env['REMOTE_USER'] != User::ROOT_USER_NAME
check_privileges("user", "w") unless request.env['REMOTE_USER'] == u
body = create_object_from_json_body
p = check_string(body[action], "Parameter '#{action}' must be a not empty string")
user = settings.mongo.user u
user.send("#{action}=", p)
settings.mongo.user_update user
create_response("Updated")
end
end
logger.info projects
raise DependencyError.new "Deleting is forbidden: User is included in #{str}"
#return [400, "Deleting is forbidden: User is included in #{str}"]
end
r = BaseRoutes.mongo.user_delete params[:user]
create_response("User '#{params[:user]}' removed")
end
# Change user privileges
#
# * *Request*
# - method : PUT
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "cmd": "command or all", -> if empty, set default privileges
# "privileges": "priv" -> 'rwx' or ''
# }
#
# * *Returns* :
# 200 - Updated
put "/user/:user" do
check_headers :accept, :content_type
check_privileges("user", "w")
data = create_object_from_json_body
user = BaseRoutes.mongo.user params[:user]
cmd = check_string(data["cmd"], "Parameter 'cmd' should be a not empty string", true) || ""
privileges = check_string(data["privileges"], "Parameter 'privileges' should be a not empty string", true) || ""
user.grant(cmd, privileges)
BaseRoutes.mongo.user_update user
create_response("Updated")
end
# Change user email/password
#
# * *Request*
# - method : PUT
# - headers :
# - Accept: application/json
# - Content-Type: application/json
# - body :
# {
# "email/password": "new user email/password",
# }
#
# * *Returns* :
# 200 - Updated
put %r{\A/user/[\w]+/(email|password)\z} do
check_headers :accept, :content_type
action = File.basename(request.path)
u = File.basename(File.dirname(request.path))
raise InvalidPrivileges.new("Access denied for '#{request.env['REMOTE_USER']}'") if u == User::ROOT_USER_NAME and request.env['REMOTE_USER'] != User::ROOT_USER_NAME
check_privileges("user", "w") unless request.env['REMOTE_USER'] == u
body = create_object_from_json_body
p = check_string(body[action], "Parameter '#{action}' must be a not empty string")
user = BaseRoutes.mongo.user u
user.send("#{action}=", p)
BaseRoutes.mongo.user_update user
create_response("Updated")
end
end
end