From c63791c7ffacb7c6cfc685e2654ffe66f0a6b185 Mon Sep 17 00:00:00 2001 From: elijah Date: Sun, 20 Mar 2016 01:13:24 -0700 Subject: api tokens: allow for special api tokens that work like session tokens but are configured in the static config, to be used for infrastructure monitoring. --- .../controller_extension/token_authentication.rb | 2 +- app/models/anonymous_user.rb | 4 ++ app/models/api_token.rb | 76 ++++++++++++++++++++++ app/models/api_user.rb | 23 +++++++ app/models/user.rb | 4 ++ config/defaults.yml | 9 +++ test/integration/api/token_test.rb | 3 +- test/unit/api_token_test.rb | 28 ++++++++ 8 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 app/models/api_token.rb create mode 100644 app/models/api_user.rb create mode 100644 test/unit/api_token_test.rb diff --git a/app/controllers/controller_extension/token_authentication.rb b/app/controllers/controller_extension/token_authentication.rb index 4ad1977..15ddef7 100644 --- a/app/controllers/controller_extension/token_authentication.rb +++ b/app/controllers/controller_extension/token_authentication.rb @@ -5,7 +5,7 @@ module ControllerExtension::TokenAuthentication def token @token ||= authenticate_with_http_token do |token, options| - Token.find_by_token(token) + Token.find_by_token(token) || ApiToken.find_by_token(token) end end diff --git a/app/models/anonymous_user.rb b/app/models/anonymous_user.rb index 5745316..e1f7a0e 100644 --- a/app/models/anonymous_user.rb +++ b/app/models/anonymous_user.rb @@ -9,6 +9,10 @@ class AnonymousUser < Object false end + def is_test? + false + end + def id nil end diff --git a/app/models/api_token.rb b/app/models/api_token.rb new file mode 100644 index 0000000..49b1870 --- /dev/null +++ b/app/models/api_token.rb @@ -0,0 +1,76 @@ +# +# Works like a regular authentication Token, but is configured in the conf file for +# use by admins or testing. +# +# This is not actually a model, but it used in the place of the normal Token model +# when appropriate +# + +require 'digest/sha2' + +class ApiToken + + # + # Searches static config to see if there is a matching api token string. + # Return an ApiToken if successful, or nil otherwise. + # + def self.find_by_token(token) + if APP_CONFIG["api_tokens"].nil? || APP_CONFIG["api_tokens"].empty? + # no api auth tokens are configured + return nil + elsif !token.is_a?(String) || token.size < 24 + # don't allow obviously invalid token strings + return nil + else + token_digest = Digest::SHA512.hexdigest(token) + username = self.static_auth_tokens[token_digest] + if username + if username == "test" + return ApiTestToken.new + elsif username == "admin" + # not yet supported + return nil + end + else + return nil + end + end + end + + private + + # + # A static hash to represent the configured api auth tokens, in the form: + # + # { + # "" => "" + # } + # + # SHA512 is used here in order to prevent an attacker from discovering + # the value for an auth token by measuring the string comparison time. + # + def self.static_auth_tokens + @static_auth_tokens ||= APP_CONFIG["api_tokens"].inject({}) {|hsh, entry| + hsh[Digest::SHA512.hexdigest(entry[1])] = entry[0] + hsh + }.freeze + end + +end + +class ApiAdminToken < Token + # not yet supported + #def authenticate + # AdminUser.new + #end +end + +# +# These tokens used by the platform to run regular monitor tests +# of a production infrastructure. +# +class ApiTestToken < Token + def authenticate + ApiTestUser.new + end +end diff --git a/app/models/api_user.rb b/app/models/api_user.rb new file mode 100644 index 0000000..d80a4d1 --- /dev/null +++ b/app/models/api_user.rb @@ -0,0 +1,23 @@ + +class ApiUser < AnonymousUser +end + +# +# A user that has limited admin access, to be used +# for running monitor tests against a live production +# installation. +# +class ApiTestUser < ApiUser + def is_test? + true + end +end + +# +# Not yet supported: +# +#class ApiAdminUser < ApiUser +# def is_admin? +# true +# end +#end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index 61793be..1aeea1c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -107,6 +107,10 @@ class User < CouchRest::Model::Base false end + def is_test? + false + end + def most_recent_tickets(count=3) Ticket.for_user(self).limit(count).all #defaults to having most recent updated first end diff --git a/config/defaults.yml b/config/defaults.yml index 906b446..84ee4c9 100644 --- a/config/defaults.yml +++ b/config/defaults.yml @@ -117,6 +117,9 @@ development: <<: *common <<: *service_levels admins: [blue, red, staff] + api_tokens: + test: nil + admin: nil domain: example.org secret_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' reraise_errors: true @@ -128,6 +131,9 @@ test: <<: *common <<: *service_levels admins: [admin, admin2] + api_tokens: + test: "212da28a59dcaca487365309dc93aa09" + admin: nil domain: test.me secret_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' reraise_errors: true @@ -142,6 +148,9 @@ production: <<: *cert_options <<: *common admins: [] + api_tokens: + test: nil + admin: nil domain: example.net engines: - support diff --git a/test/integration/api/token_test.rb b/test/integration/api/token_test.rb index ad3ac22..dafbfb7 100644 --- a/test/integration/api/token_test.rb +++ b/test/integration/api/token_test.rb @@ -1,4 +1,4 @@ -require 'test_helper' +require_relative '../../test_helper' require_relative 'srp_test' class TokenTest < SrpTest @@ -12,4 +12,5 @@ class TokenTest < SrpTest token = server_auth['token'] assert Token.find(Digest::SHA512.hexdigest(token)) end + end diff --git a/test/unit/api_token_test.rb b/test/unit/api_token_test.rb new file mode 100644 index 0000000..55d7507 --- /dev/null +++ b/test/unit/api_token_test.rb @@ -0,0 +1,28 @@ +require_relative '../test_helper' + +class ApiTokenTest < ActiveSupport::TestCase + + setup do + end + + test "api token only authenticates ApiUser" do + token_string = APP_CONFIG['api_tokens']['test'] + assert !token_string.nil? + assert !token_string.empty? + token = ApiToken.find_by_token(token_string) + user = token.authenticate + assert user, 'api token should authenticate' + assert user.is_a?(ApiUser), 'api token should return api user' + assert user.is_test?, 'api test token should return test user' + assert !user.is_admin?, 'api test token should not return admin user' + end + + test "invalid api tokens can't authenticate" do + assert_nil ApiToken.find_by_token("not a token") + with_config({"api_tokens" => {"test" => ""}}) do + assert_equal "", APP_CONFIG['api_tokens']['test'] + assert_nil ApiToken.find_by_token("") + end + end + +end \ No newline at end of file -- cgit v1.2.3