1# Copyright 2012 OpenStack Foundation 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16import base64 17import os 18import tempfile 19 20from oslo_config import cfg 21import webob 22 23from oslo_middleware import basic_auth as auth 24from oslotest import base as test_base 25 26 27class TestAuthBasic(test_base.BaseTestCase): 28 def setUp(self): 29 super().setUp() 30 31 @webob.dec.wsgify 32 def fake_app(req): 33 return webob.Response() 34 self.fake_app = fake_app 35 self.request = webob.Request.blank('/') 36 37 def write_auth_file(self, data=None): 38 if not data: 39 data = '\n' 40 with tempfile.NamedTemporaryFile(mode='w', delete=False) as f: 41 f.write(data) 42 self.addCleanup(os.remove, f.name) 43 return f.name 44 45 def test_middleware_authenticate(self): 46 auth_file = self.write_auth_file( 47 'myName:$2y$05$lE3eGtyj41jZwrzS87KTqe6.' 48 'JETVCWBkc32C63UP2aYrGoYOEpbJm\n\n\n') 49 cfg.CONF.set_override('http_basic_auth_user_file', 50 auth_file, group='oslo_middleware') 51 self.middleware = auth.BasicAuthMiddleware(self.fake_app) 52 self.request.environ[ 53 'HTTP_AUTHORIZATION'] = 'Basic bXlOYW1lOm15UGFzc3dvcmQ=' 54 response = self.request.get_response(self.middleware) 55 self.assertEqual('200 OK', response.status) 56 57 def test_middleware_unauthenticated(self): 58 auth_file = self.write_auth_file( 59 'myName:$2y$05$lE3eGtyj41jZwrzS87KTqe6.' 60 'JETVCWBkc32C63UP2aYrGoYOEpbJm\n\n\n') 61 cfg.CONF.set_override('http_basic_auth_user_file', 62 auth_file, group='oslo_middleware') 63 64 self.middleware = auth.BasicAuthMiddleware(self.fake_app) 65 response = self.request.get_response(self.middleware) 66 self.assertEqual('401 Unauthorized', response.status) 67 68 def test_authenticate(self): 69 auth_file = self.write_auth_file( 70 'foo:bar\nmyName:$2y$05$lE3eGtyj41jZwrzS87KTqe6.' 71 'JETVCWBkc32C63UP2aYrGoYOEpbJm\n\n\n') 72 # test basic auth 73 self.assertEqual( 74 {'HTTP_X_USER': 'myName', 'HTTP_X_USER_NAME': 'myName'}, 75 auth.authenticate( 76 auth_file, 'myName', b'myPassword') 77 ) 78 # test failed auth 79 e = self.assertRaises(webob.exc.HTTPBadRequest, 80 auth.authenticate, 81 auth_file, 'foo', b'bar') 82 self.assertEqual('Only bcrypt digested ' 83 'passwords are supported for foo', str(e)) 84 # test problem reading user data file 85 auth_file = auth_file + '.missing' 86 e = self.assertRaises(webob.exc.HTTPBadRequest, 87 auth.authenticate, 88 auth_file, 'myName', 89 b'myPassword') 90 self.assertEqual( 91 'Problem reading auth file', str(e)) 92 93 def test_auth_entry(self): 94 entry_pass = ('myName:$2y$05$lE3eGtyj41jZwrzS87KTqe6.' 95 'JETVCWBkc32C63UP2aYrGoYOEpbJm') 96 entry_fail = 'foo:bar' 97 # success 98 self.assertEqual( 99 {'HTTP_X_USER': 'myName', 'HTTP_X_USER_NAME': 'myName'}, 100 auth.auth_entry(entry_pass, b'myPassword') 101 ) 102 # failed, unknown digest format 103 ex = self.assertRaises(webob.exc.HTTPBadRequest, 104 auth.auth_entry, entry_fail, b'bar') 105 self.assertEqual('Only bcrypt digested ' 106 'passwords are supported for foo', str(ex)) 107 # failed, incorrect password 108 self.assertRaises(webob.exc.HTTPUnauthorized, 109 auth.auth_entry, entry_pass, b'bar') 110 111 def test_validate_auth_file(self): 112 auth_file = self.write_auth_file( 113 'myName:$2y$05$lE3eGtyj41jZwrzS87KTqe6.' 114 'JETVCWBkc32C63UP2aYrGoYOEpbJm\n\n\n') 115 # success, valid config 116 auth.validate_auth_file(auth_file) 117 # failed, missing auth file 118 auth_file = auth_file + '.missing' 119 self.assertRaises(auth.ConfigInvalid, 120 auth.validate_auth_file, auth_file) 121 # failed, invalid entry 122 auth_file = self.write_auth_file( 123 'foo:bar\nmyName:$2y$05$lE3eGtyj41jZwrzS87KTqe6.' 124 'JETVCWBkc32C63UP2aYrGoYOEpbJm\n\n\n') 125 self.assertRaises(webob.exc.HTTPBadRequest, 126 auth.validate_auth_file, auth_file) 127 128 def test_parse_token(self): 129 # success with bytes 130 token = base64.b64encode(b'myName:myPassword') 131 self.assertEqual( 132 ('myName', b'myPassword'), 133 auth.parse_token(token) 134 ) 135 # success with string 136 token = str(token, encoding='utf-8') 137 self.assertEqual( 138 ('myName', b'myPassword'), 139 auth.parse_token(token) 140 ) 141 # failed, invalid base64 142 e = self.assertRaises(webob.exc.HTTPBadRequest, 143 auth.parse_token, token[:-1]) 144 self.assertEqual('Could not decode authorization token', str(e)) 145 # failed, no colon in token 146 token = str(base64.b64encode(b'myNamemyPassword'), encoding='utf-8') 147 e = self.assertRaises(webob.exc.HTTPBadRequest, 148 auth.parse_token, token[:-1]) 149 self.assertEqual('Could not decode authorization token', str(e)) 150 151 def test_parse_header(self): 152 auth_value = 'Basic bXlOYW1lOm15UGFzc3dvcmQ=' 153 # success 154 self.assertEqual( 155 'bXlOYW1lOm15UGFzc3dvcmQ=', 156 auth.parse_header({ 157 'HTTP_AUTHORIZATION': auth_value 158 }) 159 ) 160 # failed, missing Authorization header 161 e = self.assertRaises(webob.exc.HTTPUnauthorized, 162 auth.parse_header, 163 {}) 164 # failed missing token 165 e = self.assertRaises(webob.exc.HTTPBadRequest, 166 auth.parse_header, 167 {'HTTP_AUTHORIZATION': 'Basic'}) 168 self.assertEqual('Could not parse Authorization header', str(e)) 169 # failed, type other than Basic 170 digest_value = 'Digest username="myName" nonce="foobar"' 171 e = self.assertRaises(webob.exc.HTTPBadRequest, 172 auth.parse_header, 173 {'HTTP_AUTHORIZATION': digest_value}) 174 self.assertEqual('Unsupported authorization type "Digest"', str(e)) 175