From e05a1b0f5ae40a2aa17976b3009cd563b8e4660a Mon Sep 17 00:00:00 2001 From: Azul Date: Sun, 1 May 2016 10:55:33 -0300 Subject: api: allow version bumping - bump to 2 --- app/controllers/api/certs_controller.rb | 31 +++++++ app/controllers/api/configs_controller.rb | 37 +++++++++ app/controllers/api/identities_controller.rb | 16 ++++ app/controllers/api/messages_controller.rb | 119 +++++++++++++++++++++++++++ app/controllers/api/services_controller.rb | 8 ++ app/controllers/api/sessions_controller.rb | 44 ++++++++++ app/controllers/api/smtp_certs_controller.rb | 42 ++++++++++ app/controllers/api/users_controller.rb | 83 +++++++++++++++++++ 8 files changed, 380 insertions(+) create mode 100644 app/controllers/api/certs_controller.rb create mode 100644 app/controllers/api/configs_controller.rb create mode 100644 app/controllers/api/identities_controller.rb create mode 100644 app/controllers/api/messages_controller.rb create mode 100644 app/controllers/api/services_controller.rb create mode 100644 app/controllers/api/sessions_controller.rb create mode 100644 app/controllers/api/smtp_certs_controller.rb create mode 100644 app/controllers/api/users_controller.rb (limited to 'app/controllers/api') diff --git a/app/controllers/api/certs_controller.rb b/app/controllers/api/certs_controller.rb new file mode 100644 index 0000000..46a84d3 --- /dev/null +++ b/app/controllers/api/certs_controller.rb @@ -0,0 +1,31 @@ +class Api::CertsController < ApiController + + before_filter :require_login, :unless => :anonymous_access_allowed? + before_filter :require_enabled + + # GET /cert + # deprecated - we actually create a new cert and that can + # be reflected in the action. GET /cert will eventually go + # away and be replaced by POST /cert + def show + create + end + + # POST /cert + def create + @cert = ClientCertificate.new(:prefix => service_level.cert_prefix) + render text: @cert.to_s, content_type: 'text/plain' + end + + protected + + def require_enabled + if !current_user.is_anonymous? && !current_user.enabled? + access_denied + end + end + + def service_level + current_user.effective_service_level + end +end diff --git a/app/controllers/api/configs_controller.rb b/app/controllers/api/configs_controller.rb new file mode 100644 index 0000000..55ceb4f --- /dev/null +++ b/app/controllers/api/configs_controller.rb @@ -0,0 +1,37 @@ +class Api::ConfigsController < ApiController + include ControllerExtension::JsonFile + + before_filter :require_login, :unless => :anonymous_access_allowed? + before_filter :sanitize_id, only: :show + + def index + render json: {services: service_paths} + end + + def show + send_file lookup_file + end + + protected + + SERVICE_IDS = { + soledad: "soledad-service", + eip: "eip-service", + smtp: "smtp-service" + } + + def service_paths + Hash[SERVICE_IDS.map{|k,v| [k,"/1/configs/#{v}.json"] } ] + end + + def sanitize_id + @id = params[:id].downcase + access_denied unless SERVICE_IDS.values.include? @id + end + + def lookup_file + path = APP_CONFIG[:config_file_paths][@id] + not_found if path.blank? + Rails.root.join path + end +end diff --git a/app/controllers/api/identities_controller.rb b/app/controllers/api/identities_controller.rb new file mode 100644 index 0000000..ab2ac00 --- /dev/null +++ b/app/controllers/api/identities_controller.rb @@ -0,0 +1,16 @@ +module Api + class IdentitiesController < ApiController + before_filter :token_authenticate + before_filter :require_monitor + + def show + @identity = Identity.find_by_address(params[:id]) + if @identity + respond_with @identity + else + render_not_found + end + end + + end +end diff --git a/app/controllers/api/messages_controller.rb b/app/controllers/api/messages_controller.rb new file mode 100644 index 0000000..a69a40a --- /dev/null +++ b/app/controllers/api/messages_controller.rb @@ -0,0 +1,119 @@ +module Api + class MessagesController < ApiController + + before_filter :require_login + + def index + if Dir.exist?(motd_dir) + if !CommonLanguages::available_code?(params[:locale]) + locale = 'en' + else + locale = params[:locale] + end + render json: motd_files_for_locale(locale) + else + render json: [] + end + end + + # disable per-user messages for now, not supported in the client + #def update + # if message = Message.find(params[:id]) + # message.mark_as_read_by(current_user) + # message.save + # render json: success(:marked_as_read) + # else + # render json: error(:not_found), status: :not_found + # end + #end + + private + + # + # returns list of messages, for example: + # + # [ + # {"id": 1, "locale": "en", "text": ""}, + # {"id": 2, "locale": "en", "text": ""} + # ] + # + # Each message is present only once, using the best choice + # for the locale. The order is determined by the id. + # + def motd_files_for_locale(locale) + files = [] + motd_files.keys.each do |id| + if motd_files[id].key?(locale) + msg_locale = locale + elsif motd_files[id].key?('en') + msg_locale = 'en' + else + msg_locale = motd_files[id].keys.first + end + files << { + "id" => id, + "locale" => msg_locale, + "text" => motd_files[id][msg_locale] + } + end + files.sort! {|a,b| a["id"].to_i <=> b["id"].to_i } + return files + end + + # + # returns messages of the day as a hash: + # { "1": {"en": "message"}, "2": {"en": "message"} } + # + def motd_files + if motd_changed? || @motd_files.nil? + @motd_files = load_motd_files + else + @motd_files + end + end + + def motd_changed? + newest = Dir.glob(File.join(motd_dir, '*.{html,md}')).collect{|file| File.mtime(file)}.max + if @timestamp.nil? + @timestamp = newest + return true + elsif @timestamp < newest + @timestamp = newest + return true + else + return false + end + end + + def load_motd_files + files = {} + Dir.glob(File.join(motd_dir, '*.{html,md}')).each do |file| + id, locale, msg = parse_motd_file(file) + next unless id + files[id] ||= {} + files[id][locale] = msg + end + files + end + + def parse_motd_file(file) + id, locale, ext = File.basename(file).split('.') + if id.nil? || locale.nil? || ext.nil? || id.to_i.to_s != id || !['md', 'html'].include?(ext) + Rails.logger.error "ERROR: Could not parse MOTD file #{file}" + return nil + end + contents = File.read(file) + if ext == "md" + msg = RDiscount.new(contents, :autolink).to_html + elsif ext == "html" + msg = File.read(file) + end + return id, locale, msg + end + + def motd_dir + File.join(APP_CONFIG['customization_directory'], 'motd') + end + + end +end diff --git a/app/controllers/api/services_controller.rb b/app/controllers/api/services_controller.rb new file mode 100644 index 0000000..da2774b --- /dev/null +++ b/app/controllers/api/services_controller.rb @@ -0,0 +1,8 @@ +class Api::ServicesController < ApiController + + before_filter :require_login, :unless => :anonymous_access_allowed? + + def show + respond_with current_user.effective_service_level + end +end diff --git a/app/controllers/api/sessions_controller.rb b/app/controllers/api/sessions_controller.rb new file mode 100644 index 0000000..c8deb7a --- /dev/null +++ b/app/controllers/api/sessions_controller.rb @@ -0,0 +1,44 @@ +module Api + class SessionsController < ApiController + + before_filter :require_login, only: :destroy + + def new + @session = Session.new + if authentication_errors + @errors = authentication_errors + render :status => 422 + end + end + + def create + logout if logged_in? + if params['A'] + authenticate! + else + @user = User.find_by_login(params['login']) + render :json => {salt: @user.salt} + end + end + + def update + authenticate! + @token = Token.create(:user_id => current_user.id) + session[:token] = @token.id + render :json => login_response + end + + def destroy + logout + head :no_content + end + + protected + + def login_response + handshake = session.delete(:handshake) || {} + handshake.to_hash.merge(:id => current_user.id, :token => @token.to_s) + end + + end +end diff --git a/app/controllers/api/smtp_certs_controller.rb b/app/controllers/api/smtp_certs_controller.rb new file mode 100644 index 0000000..d9eab7d --- /dev/null +++ b/app/controllers/api/smtp_certs_controller.rb @@ -0,0 +1,42 @@ +class Api::SmtpCertsController < ApiController + + before_filter :require_login + before_filter :require_email_account + before_filter :fetch_identity + before_filter :require_enabled + + # POST /1/smtp_cert + def create + @cert = ClientCertificate.new common_name: current_user.email_address + @identity.register_cert(@cert) + @identity.save + render text: @cert.to_s, content_type: 'text/plain' + end + + protected + + # + # Filters + # + + def require_email_account + access_denied unless service_level.provides? 'email' + end + + def require_enabled + access_denied unless current_user.enabled? + end + + def fetch_identity + @identity = current_user.identity + end + + # + # Helper methods + # + + def service_level + current_user.effective_service_level + end + +end diff --git a/app/controllers/api/users_controller.rb b/app/controllers/api/users_controller.rb new file mode 100644 index 0000000..e64d21f --- /dev/null +++ b/app/controllers/api/users_controller.rb @@ -0,0 +1,83 @@ +module Api + class UsersController < ApiController + include ControllerExtension::FetchUser + + # allow optional access to this controller using API auth tokens: + before_filter :token_authenticate + + before_filter :fetch_user, :only => [:update, :destroy] + before_filter :require_monitor, :only => [:index, :show] + before_filter :require_login, :only => [:index, :update, :destroy] + + respond_to :json + + # used for autocomplete for admins in the web ui + def index + if params[:query] + @users = User.login_starts_with(params[:query]) + respond_with @users.map(&:login).sort + else + render :json => {'error' => 'query required', 'status' => :unprocessable_entity} + end + end + + def show + if params[:login] + @user = User.find_by_login(params[:login]) + elsif params[:id] + @user = User.find(params[:id]) + end + if @user + respond_with @user + else + not_found + end + end + + def create + if current_user.is_monitor? + create_test_account + elsif APP_CONFIG[:allow_registration] + create_account + else + head :forbidden + end + end + + def update + @user.account.update params[:user] + respond_with @user + end + + def destroy + destroy_identity = current_user.is_monitor? || params[:identities] == "destroy" + @user.account.destroy(destroy_identity) + if @user == current_user + logout + end + render :json => {'success' => 'user deleted'} + end + + private + + # tester auth can only create test users. + def create_test_account + if User::is_test?(params[:user][:login]) + @user = Account.create(params[:user], :invite_required => false) + respond_with @user + else + head :forbidden + end + end + + def create_account + if APP_CONFIG[:allow_registration] + @user = Account.create(params[:user]) + respond_with @user # return ID instead? + else + head :forbidden + end + end + + end +end -- cgit v1.2.3 From 83f59164fc069f2593cf6babbc18638d9a68c9a3 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 18 May 2016 20:21:04 +0200 Subject: features for API version 2 - keep old ones Now we test both api versions. We want this for backwards compatibility. --- app/controllers/api/configs_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'app/controllers/api') diff --git a/app/controllers/api/configs_controller.rb b/app/controllers/api/configs_controller.rb index 55ceb4f..0f9b8a6 100644 --- a/app/controllers/api/configs_controller.rb +++ b/app/controllers/api/configs_controller.rb @@ -21,7 +21,11 @@ class Api::ConfigsController < ApiController } def service_paths - Hash[SERVICE_IDS.map{|k,v| [k,"/1/configs/#{v}.json"] } ] + Hash[SERVICE_IDS.map{|k,v| [k,"/#{api_version}/configs/#{v}.json"] } ] + end + + def api_version + ["1", "2"].include?(params[:version]) ? params[:version] : "2" end def sanitize_id -- cgit v1.2.3 From ad208ae3625e67c2551744df7906ebdda94d215e Mon Sep 17 00:00:00 2001 From: Azul Date: Mon, 23 May 2016 12:27:14 +0200 Subject: rename destroy_identity to release_handles This expresses the intent rather than the implementation. Also replace temp with query refactoring. --- app/controllers/api/users_controller.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app/controllers/api') diff --git a/app/controllers/api/users_controller.rb b/app/controllers/api/users_controller.rb index e64d21f..c79a729 100644 --- a/app/controllers/api/users_controller.rb +++ b/app/controllers/api/users_controller.rb @@ -50,8 +50,7 @@ module Api end def destroy - destroy_identity = current_user.is_monitor? || params[:identities] == "destroy" - @user.account.destroy(destroy_identity) + @user.account.destroy(release_handles) if @user == current_user logout end @@ -60,6 +59,10 @@ module Api private + def release_handles + current_user.is_monitor? || params[:identities] == "destroy" + end + # tester auth can only create test users. def create_test_account if User::is_test?(params[:user][:login]) -- cgit v1.2.3 From ab1917c5fe0f03e7719863a5598ad575d9fef302 Mon Sep 17 00:00:00 2001 From: NavaL Date: Thu, 14 Jul 2016 15:06:20 +0200 Subject: [feature] restrict is_admin in the user api, to only allow querying for him/herself So that it we do not expose the is_admin property to anyone else including other admins. --- app/controllers/api/users_controller.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'app/controllers/api') diff --git a/app/controllers/api/users_controller.rb b/app/controllers/api/users_controller.rb index c79a729..709e076 100644 --- a/app/controllers/api/users_controller.rb +++ b/app/controllers/api/users_controller.rb @@ -28,12 +28,20 @@ module Api @user = User.find(params[:id]) end if @user - respond_with @user + respond_with user_response else not_found end end + def user_response + @user.to_hash.tap do |user_hash| + if @user == current_user + user_hash['is_admin'] = @user.is_admin? + end + end + end + def create if current_user.is_monitor? create_test_account -- cgit v1.2.3 From fbad882075e745ab7afbe5f89c67544fb3c607c3 Mon Sep 17 00:00:00 2001 From: Azul Date: Thu, 18 Aug 2016 11:00:16 +0200 Subject: respond_to on a per controller basis If you inherit respond to and call it again in your controller it will not overwrite the previous but add to it. Since we always have some exceptions from the rules it's probably easiest to be explicit in the controllers that require it themselves. --- app/controllers/api/identities_controller.rb | 2 ++ app/controllers/api/services_controller.rb | 2 ++ app/controllers/api/sessions_controller.rb | 1 + 3 files changed, 5 insertions(+) (limited to 'app/controllers/api') diff --git a/app/controllers/api/identities_controller.rb b/app/controllers/api/identities_controller.rb index ab2ac00..de4910a 100644 --- a/app/controllers/api/identities_controller.rb +++ b/app/controllers/api/identities_controller.rb @@ -3,6 +3,8 @@ module Api before_filter :token_authenticate before_filter :require_monitor + respond_to :json + def show @identity = Identity.find_by_address(params[:id]) if @identity diff --git a/app/controllers/api/services_controller.rb b/app/controllers/api/services_controller.rb index da2774b..58e129c 100644 --- a/app/controllers/api/services_controller.rb +++ b/app/controllers/api/services_controller.rb @@ -2,6 +2,8 @@ class Api::ServicesController < ApiController before_filter :require_login, :unless => :anonymous_access_allowed? + respond_to :json + def show respond_with current_user.effective_service_level end diff --git a/app/controllers/api/sessions_controller.rb b/app/controllers/api/sessions_controller.rb index c8deb7a..178f86e 100644 --- a/app/controllers/api/sessions_controller.rb +++ b/app/controllers/api/sessions_controller.rb @@ -2,6 +2,7 @@ module Api class SessionsController < ApiController before_filter :require_login, only: :destroy + respond_to :json def new @session = Session.new -- cgit v1.2.3