summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenry Nash <henryn@linux.vnet.ibm.com>2013-03-04 05:05:15 +0000
committerHenry Nash <henryn@linux.vnet.ibm.com>2013-03-11 11:50:09 +0000
commitd782a998474d92d4299b4404b69442f0288efc3b (patch)
tree4ada0437add2a199329f77b0043cd4094f20b4a1
parentae36809fdecdedd09abf23eeadf2374c77b8a8df (diff)
downloadpython-keystoneclient-d782a998474d92d4299b4404b69442f0288efc3b.tar.gz
Fix auth-token middleware to understand v3 tokens
Now that the Identity server supports v3 tokens, the auth_token middleware should permit the in-line validation of such a token. This essentially means just setting any new environment items that correspond to the new attributes that may be in a v3 token (such as domains), as well as allowing for the slight format differences. Most of the work in this change is actually in the unit tests, where it was important to try and enable the existing tests to be run against an auth_token middleware configured for both v2 and v3. This meant restructing the test class so that the token format is separated from the individual tests and is initialized by the class Setup(). Since there are some new signed token formats included in this testing, a new set of the signed tokens was generated. Fixes Bug #1132390 Change-Id: I78b232d30f5310c39089fbbc8e56c23df291f89f
-rw-r--r--examples/pki/certs/cacert.pem20
-rw-r--r--examples/pki/certs/middleware.pem44
-rw-r--r--examples/pki/certs/signing_cert.pem16
-rw-r--r--examples/pki/certs/ssl_cert.pem16
-rw-r--r--examples/pki/cms/auth_token_revoked.pem8
-rw-r--r--examples/pki/cms/auth_token_scoped.pem6
-rw-r--r--examples/pki/cms/auth_token_unscoped.pem8
-rw-r--r--examples/pki/cms/auth_v3_token_revoked.json11
-rw-r--r--examples/pki/cms/auth_v3_token_revoked.pem44
-rw-r--r--examples/pki/cms/auth_v3_token_scoped.json11
-rw-r--r--examples/pki/cms/auth_v3_token_scoped.pem42
-rw-r--r--examples/pki/cms/revocation_list.pem6
-rwxr-xr-xexamples/pki/gen_pki.sh2
-rw-r--r--examples/pki/private/cakey.pem28
-rw-r--r--examples/pki/private/signing_key.pem28
-rw-r--r--examples/pki/private/ssl_key.pem28
-rw-r--r--keystoneclient/middleware/auth_token.py255
-rw-r--r--tests/test_auth_token_middleware.py863
18 files changed, 1080 insertions, 356 deletions
diff --git a/examples/pki/certs/cacert.pem b/examples/pki/certs/cacert.pem
index 8cf663c..6075f2e 100644
--- a/examples/pki/certs/cacert.pem
+++ b/examples/pki/certs/cacert.pem
@@ -1,18 +1,18 @@
-----BEGIN CERTIFICATE-----
-MIIC0TCCAjqgAwIBAgIJAP2TNFqmE1KUMA0GCSqGSIb3DQEBBQUAMIGeMQowCAYD
+MIIC0TCCAjqgAwIBAgIJAK6p/UfYvENdMA0GCSqGSIb3DQEBBQUAMIGeMQowCAYD
VQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55
dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMG
CSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2Vs
-ZiBTaWduZWQwIBcNMTIxMTExMTA1NDA2WhgPMjA3MTA1MDYxMDU0MDZaMIGeMQow
+ZiBTaWduZWQwIBcNMTMwMzA3MTcxMzEyWhgPMjA3MTA4MzAxNzEzMTJaMIGeMQow
CAYDVQQFEwE1MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1
bm55dmFsZTESMBAGA1UEChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTEl
MCMGCSqGSIb3DQEJARYWa2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxML
-U2VsZiBTaWduZWQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMXgnd5wlHAp
-GxZ58LrpEkHU995lT9PxtMgkp0tpFhg7R5HQw9K7TfQk5NHB28hNzf8UE/c0z2pJ
-XggPnAzvdx27NQeJGX5CWsi6fITZ8vH/+SxgfxxC+CE/6BkDpzw21MgBtq11vWL7
-XVaxNeU12Ax889U66i3CrObuCYt2mbpzAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB
-Af8wDQYJKoZIhvcNAQEFBQADgYEAkFIbnr2/0/XWp+f80Gl6GAC7tdmZFlT9udVF
-q794rXyMlYY64pq34SzfQAn+4DztT4B9yzrTx03tLNr6Uf+5TS+ubcwG41UBBMs/
-Icf9zBMRqr+IXhijS49gQ7dPjqNTCqX+6ILbRWjdXP15ZWymI3ayQL/CMwFt/E+0
-kT6MLes=
+U2VsZiBTaWduZWQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOw4quFzQ/xb
+UOKuLtXdiZLPA0Wi38iGEa+T8tp7j3US44wAamckdZb4cq9/Qx03EBKd2mcJvUoP
+rLnSlnHQMH2VGA1whZpZTWqt8ydQdDYB1SUKeUoxcjq8EKl8X8Sd3dP5amlyFCOI
+GVhFyAXYgaYlmf+s6FIzpY55Uy2zX+nZAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB
+Af8wDQYJKoZIhvcNAQEFBQADgYEAp5nII86N8ISu2FGEW/Ja7zU0diZpv7h/8enR
+06uwksv722ArOzQ22Y0xezZN3TEc5GVKPbHPSXfvvha09K5QlIp9idLy65Mu/DXa
+Fo+kJoq7rMW6Det/mOoWp3O4zgYlxvKTFjyNo300nRir4nvHmbrF/vhXVqDm2roS
+vLoyVvY=
-----END CERTIFICATE-----
diff --git a/examples/pki/certs/middleware.pem b/examples/pki/certs/middleware.pem
index ae5b8db..4e840fa 100644
--- a/examples/pki/certs/middleware.pem
+++ b/examples/pki/certs/middleware.pem
@@ -3,31 +3,31 @@ MIICoTCCAgoCARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK
EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr
ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x
-MjExMTExMDU0MDZaGA8yMDcxMDUwNjEwNTQwNlowgZAxCzAJBgNVBAYTAlVTMQsw
+MzAzMDcxNzEzMTJaGA8yMDcxMDgzMDE3MTMxMlowgZAxCzAJBgNVBAYTAlVTMQsw
CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh
Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv
cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEB
-BQADgY0AMIGJAoGBALVu4bjaOH33yAx0WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+
-cUriQtxf6ahjxtzLPERS81SnwZmrICWZngbOn733pULMTZktTJH+o7C74NdKwUSN
-xjlCeWUy+FqIQoje4ygoJRPpMdkp1wHNO0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMB
-AAEwDQYJKoZIhvcNAQEFBQADgYEAcp9ancue9Oq+MkaPucCrIqFhiUsdUThulJlB
-etPpUDGgStBSHgze/oxG2+flIjRoI6gG9Chfw//vWHOwDT7N32AHSgaI4b8/k/+s
-hAV2khYkV4PW2oS1TfeU/vxQzXbgApqhLBNqfFmJVW48aGAr/aqsJi3MYWN3269+
-6vChaVw=
+BQADgY0AMIGJAoGBAM323GVGJ6UImf6nfz9P+9MURBo0okaV/3ewyfSMri8DbM0s
+CqDtC43R1jIrHtEdnUU7kHguFXc09p9pHSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ
+8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKUC1ifHcleI2dVMW+LIkDhXPEc4XO1AgMB
+AAEwDQYJKoZIhvcNAQEFBQADgYEAchynxfP/FQC8FNhKs/dGI196qBq4MVobvNjQ
+trdLAjbZwp1/i6SHLxXEDm9bIWyInE7D8hGqXXQAImzAaH0t3oYR3C4XQWOSPPwU
+6tamnsXDVR2w3aHbEh6AuIahZQaau5tnGopwiWRDNZllbSlfay60r6Vj4ex5LtVM
+eBLz1Jg=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
-MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALVu4bjaOH33yAx0
-WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+cUriQtxf6ahjxtzLPERS81SnwZmrICWZ
-ngbOn733pULMTZktTJH+o7C74NdKwUSNxjlCeWUy+FqIQoje4ygoJRPpMdkp1wHN
-O0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMBAAECgYAmwq6EYFJrTvE0//JmN/8qzfvg
-dI5PoWpD+F8UInUxr2T2tHOdrOLd07vGVrKYXu7cJeCIOGKa4r02azAggioL/nE9
-FgPpqEC+QROvLuhFsk1gLZ2pGQ06sveKZVMH22h59BKZkYlhjh5qd4vlmhPqkmPp
-gdXj7ZjDCJhhQdFVkQJBANp18k2mVksn8q29LMieVTSIZNN3ucDA1QHbim+3fp/O
-GxCzU7Mv1Xfnu1zoRFu5/sF3YG0Zy3TGPDrEljBC3rUCQQDUnBjVFXL35OkBZqXW
-taJPzGbsPoqAO+Ls2juS97zNzeGxUNhvcKuEvHO63PXqDxp1535DpvJEBN1rT2FF
-iaO1AkEAt/QTWWFUTqrPxY6DNFdm5fpn9E1fg7icZJkKBDJeFJCH59MpCryfovzl
-n0ERtq9ynlQ4RQYwdR8rvkylLvRP9QJAOiXHFOAc5XeR0nREfwiGL9TzgUFJl/DJ
-C4ZULMnctVzNkTVPPItQHal87WppR26CCiUZ/161e6zo8eRv8hjG0QJABWqfYQuK
-dWH8nxlXS+NFUDbsCdL+XpOVE7iEH7hvSw/A/kz40mLx8sDp/Fz1ysrogR/L+NGC
-Vrlwm4q/WYJO0Q==
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM323GVGJ6UImf6n
+fz9P+9MURBo0okaV/3ewyfSMri8DbM0sCqDtC43R1jIrHtEdnUU7kHguFXc09p9p
+HSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKU
+C1ifHcleI2dVMW+LIkDhXPEc4XO1AgMBAAECgYBOZfMKkaOxjA6iAjvLa7Sdag9q
+MjK6z4nIk4CsF4iN2K3ngyYgj1pgh0kTG5rFWpJssfmR5WjCUWS21RoEptDeZf/A
+jRqzW3u493JAhyOjTK4DYbB9CwCmeGkoImC3nn2PrBgG1OPrSQMB3ODqVA2Pa1eF
+omqKQmAqCCijtmllmQJBAOnpN3sjykUlGVWY7HxdBAOsQ5DkkCXL6ZSjA3pRYvJQ
+12pKELZyxZ8GtVCFvOjaCpdxL+1MsRHkEfZpWz9o9BsCQQDhagjUFbgAQzo/TH1X
+iblrnWUi7rs+IIDOF48qy/t1FKFlyCHbMYQLB/rPSN1G+5uMEapCuOBpVQsO9v5n
+wJRvAkBQXOPG1sEDiH9vvR5ii8J5UJHWEfDES45wlqD3QUbxYXzg85lSVZQ30qIw
+jAIfLeo9pZGFwbeEIgtZ0VCcNH7JAkBK3FEkRjY+eBUvEnMKEGYw9CuzZz9uCZNd
+Xnughe/z2S8kw0tjJVWp6DOGhbdfLI5i/TbjQ8zbjm/Gv4aL5GwnAkEA42UWJKNQ
+ztq73xmVmihToMjMe6k2DDPQpq+e2b/522Vz1ZDJlIV9tpoykFX2XiPnRz1o1oWd
+DXQBvYeFzthvKA==
-----END PRIVATE KEY-----
diff --git a/examples/pki/certs/signing_cert.pem b/examples/pki/certs/signing_cert.pem
index 1491b55..a3b141e 100644
--- a/examples/pki/certs/signing_cert.pem
+++ b/examples/pki/certs/signing_cert.pem
@@ -3,15 +3,15 @@ MIICoDCCAgkCAREwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK
EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr
ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x
-MjExMTExMDU0MDZaGA8yMDcxMDUwNjEwNTQwNlowgY8xCzAJBgNVBAYTAlVTMQsw
+MzAzMDcxNzEzMTJaGA8yMDcxMDgzMDE3MTMxMlowgY8xCzAJBgNVBAYTAlVTMQsw
CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh
Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv
cGVuc3RhY2sub3JnMREwDwYDVQQDEwhLZXlzdG9uZTCBnzANBgkqhkiG9w0BAQEF
-AAOBjQAwgYkCgYEAuoQC6IBqMxC5845c/ZkLsdcQbTHqIpYJHEkwEoxyeEjwiGFf
-iZmiZ91pSFNc9MfjdJnN+be/ndVS19w1nrrJvV/udVsf6JZWkTPX5HyxnllwznCH
-pP7gfvMZzGsqzWlSdiD6mcRbCYRX9hCCauG3jhCtISINCVYMYQGH6QSib9sCAwEA
-ATANBgkqhkiG9w0BAQUFAAOBgQBCssELi+1RSjEmzeqSnpgUqmtpvB9oxbcwl+xH
-rIrYvqMU6pV2aSxgLDqpGjjusLHUau9Bmu3Myc/fm9/mlPUQHNj0AWl8vvfSlq1b
-vsWMUa1h4UFlPWoF2DIUFd+noBxe5CbcLUV6K0oyJAcPO433OyuGl5oQkhxmoy1J
-w59KRg==
+AAOBjQAwgYkCgYEApibRgDiDl4u73oeVQjkiNBN+VYYSQ82UJoQvuoYbzYndAik9
+P63vf42lu2tSMs8U/oNl/EqHvI92rZhGpzr9wRVAQuaKYlrPk1Sn9hJHFjjotSHY
+Sq+ivlG7WmLoIrQkYYzFr3r+yiiYtzL0cv68objKEwGMZasn95nJSjqAxFUCAwEA
+ATANBgkqhkiG9w0BAQUFAAOBgQAmFHIcvPC3G+DFM8Ke8kZi/UGl4ugUlkdIVmCG
+yokdR0b7v72r8ocQ/QSIRcw/Y0t3lPsAt1Dq6m2zN8PAC30m4QQqCu4o1xEWU51N
+sUfNaw55qjpYEpZ2DmUjJc0kzYIsmaDkqM4t5lTJ7K7+zoWdW9joJV+VAyEq6NiS
+RhjOeg==
-----END CERTIFICATE-----
diff --git a/examples/pki/certs/ssl_cert.pem b/examples/pki/certs/ssl_cert.pem
index 0a0bc21..784638f 100644
--- a/examples/pki/certs/ssl_cert.pem
+++ b/examples/pki/certs/ssl_cert.pem
@@ -3,15 +3,15 @@ MIICoTCCAgoCARAwDQYJKoZIhvcNAQEFBQAwgZ4xCjAIBgNVBAUTATUxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQK
EwlPcGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZr
ZXlzdG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZDAgFw0x
-MjExMTExMDU0MDZaGA8yMDcxMDUwNjEwNTQwNlowgZAxCzAJBgNVBAYTAlVTMQsw
+MzAzMDcxNzEzMTJaGA8yMDcxMDgzMDE3MTMxMlowgZAxCzAJBgNVBAYTAlVTMQsw
CQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3Rh
Y2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBv
cGVuc3RhY2sub3JnMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEB
-BQADgY0AMIGJAoGBALVu4bjaOH33yAx0WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+
-cUriQtxf6ahjxtzLPERS81SnwZmrICWZngbOn733pULMTZktTJH+o7C74NdKwUSN
-xjlCeWUy+FqIQoje4ygoJRPpMdkp1wHNO0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMB
-AAEwDQYJKoZIhvcNAQEFBQADgYEAcp9ancue9Oq+MkaPucCrIqFhiUsdUThulJlB
-etPpUDGgStBSHgze/oxG2+flIjRoI6gG9Chfw//vWHOwDT7N32AHSgaI4b8/k/+s
-hAV2khYkV4PW2oS1TfeU/vxQzXbgApqhLBNqfFmJVW48aGAr/aqsJi3MYWN3269+
-6vChaVw=
+BQADgY0AMIGJAoGBAM323GVGJ6UImf6nfz9P+9MURBo0okaV/3ewyfSMri8DbM0s
+CqDtC43R1jIrHtEdnUU7kHguFXc09p9pHSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ
+8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKUC1ifHcleI2dVMW+LIkDhXPEc4XO1AgMB
+AAEwDQYJKoZIhvcNAQEFBQADgYEAchynxfP/FQC8FNhKs/dGI196qBq4MVobvNjQ
+trdLAjbZwp1/i6SHLxXEDm9bIWyInE7D8hGqXXQAImzAaH0t3oYR3C4XQWOSPPwU
+6tamnsXDVR2w3aHbEh6AuIahZQaau5tnGopwiWRDNZllbSlfay60r6Vj4ex5LtVM
+eBLz1Jg=
-----END CERTIFICATE-----
diff --git a/examples/pki/cms/auth_token_revoked.pem b/examples/pki/cms/auth_token_revoked.pem
index 0bd7a70..6136f6d 100644
--- a/examples/pki/cms/auth_token_revoked.pem
+++ b/examples/pki/cms/auth_token_revoked.pem
@@ -35,8 +35,8 @@ ZXJuYW1lMSJ9fX0NCjGCAUkwggFFAgEBMIGkMIGeMQowCAYDVQQFEwE1MQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN1bm55dmFsZTESMBAGA1UE
ChMJT3BlblN0YWNrMREwDwYDVQQLEwhLZXlzdG9uZTElMCMGCSqGSIb3DQEJARYW
a2V5c3RvbmVAb3BlbnN0YWNrLm9yZzEUMBIGA1UEAxMLU2VsZiBTaWduZWQCAREw
-BwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEgYBhV5KrVjcdACPUNafkPY+lgCSlh6uc
-N55SATBcQmg1/argEUFg/cx2GcF7ftQV384iGepLEgsq+6om2wPw6DWA0RknpVLJ
-vMsHbWdGoXIZ5jRuAQTPtkXcJQOR677baDHvGJ+5zwBBDT2CmN2Tcv348+Xpjp7D
-hF/cmAXnYYo00g==
+BwYFKw4DAhowDQYJKoZIhvcNAQEBBQAEgYB3HICZ2Jj9edPkhmic5Td/qzod2FpQ
+tB5EUL32Qw33FrMo6ALxG2znmiR3F2rf2kSmOVpBRQgysnkVXjDGPuBt/qMq41VR
+NvvoM+Cf2HtDYGFvyyO3QNRf9NLaFije71pRQUBFR8iEz0zjvdouyuHVZsbQuke5
+XdEgB8F3fQ6/Pg==
-----END CMS-----
diff --git a/examples/pki/cms/auth_token_scoped.pem b/examples/pki/cms/auth_token_scoped.pem
index 529d7f8..20c6929 100644
--- a/examples/pki/cms/auth_token_scoped.pem
+++ b/examples/pki/cms/auth_token_scoped.pem
@@ -35,7 +35,7 @@ AQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTES
MBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sxETAPBgNVBAsT
CEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVuc3RhY2sub3Jn
MRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjANBgkqhkiG9w0BAQEF
-AASBgFizBVs3dCvlHx04nUHgXHpaA9RL+e3uaaNszK9UwCBpBlv8c6+74sz6i3+G
-eYDIpL9bc6QgNJ6cKhmW5yLmS8/+mmAMAcm06bdWc7p/mqC3Ild+xmQ+OHDYyyJg
-DvtRUgtidFUCvxne/nwKK0WHJlpY+iwWqel5F+Xqmb8vheb1
+AASBgFbBja47P7p32dQ+wAXKDn9/JL/RjImAKvT/f8bBZxmc+SbnmpDd0lwH44eE
+cVFfq55Ny0+SmYaLP6ZgtvGYpiP9TqxuySHQP1EKxAmIFA2yRa3YTviTsSvH0OCC
+WEnlYLxxdqh97whF3H5bDOMh6aVEyHPRS2m8oOqcPW+5o4gX
-----END CMS-----
diff --git a/examples/pki/cms/auth_token_unscoped.pem b/examples/pki/cms/auth_token_unscoped.pem
index d42d1f1..1c284a1 100644
--- a/examples/pki/cms/auth_token_unscoped.pem
+++ b/examples/pki/cms/auth_token_unscoped.pem
@@ -10,8 +10,8 @@ dXNlcl9uYW1lMSJ9fX0xggFJMIIBRQIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkG
A1UEBhMCVVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNV
BAoTCU9wZW5TdGFjazERMA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEW
FmtleXN0b25lQG9wZW5zdGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgER
-MAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIGAITCwkW7cAbcWbCBD5GfGMGHB9hP/
-UagaCZ8HFhlzjdQoJjvC+Mtu+3lWlwqPGR8ztY9kBc1401S2qJxD4FGo+M3CkNpF
-s0mtaT2PUJfFkDCzHqeBQNFHyZeqLjkPYnokPcw4s3i60DBGTFfAiUT3xumn8a4h
-C+zEAee35C/A+Iw=
+MAcGBSsOAwIaMA0GCSqGSIb3DQEBAQUABIGARUpIQsA8a2g9HC1ZjpX37oXZz/3n
+hdpRUyKTWbjd2mi2rC68DVnyHLhZ3SQfBN896fPG1HW6LFuFrserYwBYVrX1rGDz
+OS2dBigPzeP1301X3IRdbDrnMvzmEX2eTSmBEZ/CMMOXTTSYAYutyOtzATW9v639
+rRT4L/yQFcIxfCo=
-----END CMS-----
diff --git a/examples/pki/cms/auth_v3_token_revoked.json b/examples/pki/cms/auth_v3_token_revoked.json
new file mode 100644
index 0000000..0807f86
--- /dev/null
+++ b/examples/pki/cms/auth_v3_token_revoked.json
@@ -0,0 +1,11 @@
+{"token":
+ {"catalog": [{"endpoints": [{"adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "volume", "name": "volume"},
+ {"endpoints": [{"adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1"}], "endpoints_links": [], "type": "image", "name": "glance"},
+ {"endpoints": [{"adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "compute", "name": "nova"},
+ {"endpoints": [{"adminURL": "http://127.0.0.1:35357/v3", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v3", "publicURL": "http://127.0.0.1:5000/v3"}], "endpoints_links": [], "type": "identity", "name": "keystone"}],
+ "expires": "2012-06-02T14:47:34Z",
+ "project": {"enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}},
+ "user": {"name": "revoked_username1", "id": "revoked_user_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}},
+ "roles": [{"name": "role1"}, {"name": "role2"}]
+ }
+}
diff --git a/examples/pki/cms/auth_v3_token_revoked.pem b/examples/pki/cms/auth_v3_token_revoked.pem
new file mode 100644
index 0000000..02e73b0
--- /dev/null
+++ b/examples/pki/cms/auth_v3_token_revoked.pem
@@ -0,0 +1,44 @@
+-----BEGIN CMS-----
+MIIHsgYJKoZIhvcNAQcCoIIHozCCB58CAQExCTAHBgUrDgMCGjCCBkAGCSqGSIb3
+DQEHAaCCBjEEggYteyJ0b2tlbiI6DQogICAgeyJjYXRhbG9nIjogW3siZW5kcG9p
+bnRzIjogW3siYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0
+YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwgInJlZ2lvbiI6ICJyZWdp
+b25PbmUiLCAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc2L3Yx
+LzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwgInB1YmxpY1VSTCI6
+ICJodHRwOi8vMTI3LjAuMC4xOjg3NzYvdjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBm
+Y2Y4OWJiNjYxN2EifV0sICJlbmRwb2ludHNfbGlua3MiOiBbXSwgInR5cGUiOiAi
+dm9sdW1lIiwgIm5hbWUiOiAidm9sdW1lIn0sDQogICAgICAgICAgICAgICAgIHsi
+ZW5kcG9pbnRzIjogW3siYWRtaW5VUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5Mjky
+L3YxIiwgInJlZ2lvbiI6ICJyZWdpb25PbmUiLCAiaW50ZXJuYWxVUkwiOiAiaHR0
+cDovLzEyNy4wLjAuMTo5MjkyL3YxIiwgInB1YmxpY1VSTCI6ICJodHRwOi8vMTI3
+LjAuMC4xOjkyOTIvdjEifV0sICJlbmRwb2ludHNfbGlua3MiOiBbXSwgInR5cGUi
+OiAiaW1hZ2UiLCAibmFtZSI6ICJnbGFuY2UifSwNCiAgICAgICAgICAgICAgICAg
+eyJlbmRwb2ludHMiOiBbeyJhZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3
+NzQvdjEuMS82NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJyZWdp
+b24iOiAicmVnaW9uT25lIiwgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w
+LjE6ODc3NC92MS4xLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNmODliYjY2MTdhIiwg
+InB1YmxpY1VSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm
+YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSJ9XSwgImVuZHBvaW50c19saW5rcyI6
+IFtdLCAidHlwZSI6ICJjb21wdXRlIiwgIm5hbWUiOiAibm92YSJ9LA0KICAgICAg
+ICAgICAgICAgICB7ImVuZHBvaW50cyI6IFt7ImFkbWluVVJMIjogImh0dHA6Ly8x
+MjcuMC4wLjE6MzUzNTcvdjMiLCAicmVnaW9uIjogIlJlZ2lvbk9uZSIsICJpbnRl
+cm5hbFVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwgInB1YmxpY1VS
+TCI6ICJodHRwOi8vMTI3LjAuMC4xOjUwMDAvdjMifV0sICJlbmRwb2ludHNfbGlu
+a3MiOiBbXSwgInR5cGUiOiAiaWRlbnRpdHkiLCAibmFtZSI6ICJrZXlzdG9uZSJ9
+XSwNCiAgICAgImV4cGlyZXMiOiAiMjAxMi0wNi0wMlQxNDo0NzozNFoiLA0KICAg
+ICAicHJvamVjdCI6IHsiZW5hYmxlZCI6IHRydWUsICJkZXNjcmlwdGlvbiI6IG51
+bGwsICJuYW1lIjogInRlbmFudF9uYW1lMSIsICJpZCI6ICJ0ZW5hbnRfaWQxIiwg
+ImRvbWFpbiI6IHsiaWQiOiAiZG9tYWluX2lkMSIsICJuYW1lIjogImRvbWFpbl9u
+YW1lMSJ9fSwNCiAgICAgInVzZXIiOiB7Im5hbWUiOiAicmV2b2tlZF91c2VybmFt
+ZTEiLCAiaWQiOiAicmV2b2tlZF91c2VyX2lkMSIsICJkb21haW4iOiB7ImlkIjog
+ImRvbWFpbl9pZDEiLCAibmFtZSI6ICJkb21haW5fbmFtZTEifX0sDQogICAgICJy
+b2xlcyI6IFt7Im5hbWUiOiAicm9sZTEifSwgeyJuYW1lIjogInJvbGUyIn1dDQog
+ICAgfQ0KfQ0KMYIBSTCCAUUCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYT
+AlVTMQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlP
+cGVuU3RhY2sxETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlz
+dG9uZUBvcGVuc3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUr
+DgMCGjANBgkqhkiG9w0BAQEFAASBgD8S/YsERhsYgxNTHQ+AohaWBCxS2eMJDG1e
+lZBabrMHra5DkP5PAeHKApcagNo4UfcN9dVeGFi+VzHD/lLHaR1r1VI0SiSb+pQ4
+dTZGEtMVsfPbReWS9RaLt3YePGkZ410Nhx2STF1kmMmVhGGXDzyMbIGGQu6BmQsF
+G8+izx9v
+-----END CMS-----
diff --git a/examples/pki/cms/auth_v3_token_scoped.json b/examples/pki/cms/auth_v3_token_scoped.json
new file mode 100644
index 0000000..23ad0e3
--- /dev/null
+++ b/examples/pki/cms/auth_v3_token_scoped.json
@@ -0,0 +1,11 @@
+{"token":
+ {"catalog": [{"endpoints": [{"adminURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8776/v1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "volume", "name": "volume"},
+ {"endpoints": [{"adminURL": "http://127.0.0.1:9292/v1", "region": "regionOne", "internalURL": "http://127.0.0.1:9292/v1", "publicURL": "http://127.0.0.1:9292/v1"}], "endpoints_links": [], "type": "image", "name": "glance"},
+ {"endpoints": [{"adminURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "region": "regionOne", "internalURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a", "publicURL": "http://127.0.0.1:8774/v1.1/64b6f3fbcc53435e8a60fcf89bb6617a"}], "endpoints_links": [], "type": "compute", "name": "nova"},
+ {"endpoints": [{"adminURL": "http://127.0.0.1:35357/v3", "region": "RegionOne", "internalURL": "http://127.0.0.1:35357/v3", "publicURL": "http://127.0.0.1:5000/v3"}], "endpoints_links": [], "type": "identity", "name": "keystone"}],
+ "expires": "2012-06-02T14:47:34Z",
+ "project": {"enabled": true, "description": null, "name": "tenant_name1", "id": "tenant_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}},
+ "user": {"name": "user_name1", "id": "user_id1", "domain": {"id": "domain_id1", "name": "domain_name1"}},
+ "roles": [{"name": "role1"}, {"name": "role2"}]
+ }
+}
diff --git a/examples/pki/cms/auth_v3_token_scoped.pem b/examples/pki/cms/auth_v3_token_scoped.pem
new file mode 100644
index 0000000..e289ab1
--- /dev/null
+++ b/examples/pki/cms/auth_v3_token_scoped.pem
@@ -0,0 +1,42 @@
+-----BEGIN CMS-----
+MIIHeAYJKoZIhvcNAQcCoIIHaTCCB2UCAQExCTAHBgUrDgMCGjCCBgYGCSqGSIb3
+DQEHAaCCBfcEggXzeyJ0b2tlbiI6IA0KCXsiY2F0YWxvZyI6IFt7ImVuZHBvaW50
+cyI6IFt7ImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82NGI2
+ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJyZWdpb24iOiAicmVnaW9u
+T25lIiwgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6ODc3Ni92MS82
+NGI2ZjNmYmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJwdWJsaWNVUkwiOiAi
+aHR0cDovLzEyNy4wLjAuMTo4Nzc2L3YxLzY0YjZmM2ZiY2M1MzQzNWU4YTYwZmNm
+ODliYjY2MTdhIn1dLCAiZW5kcG9pbnRzX2xpbmtzIjogW10sICJ0eXBlIjogInZv
+bHVtZSIsICJuYW1lIjogInZvbHVtZSJ9LA0KCQkJICAgICB7ImVuZHBvaW50cyI6
+IFt7ImFkbWluVVJMIjogImh0dHA6Ly8xMjcuMC4wLjE6OTI5Mi92MSIsICJyZWdp
+b24iOiAicmVnaW9uT25lIiwgImludGVybmFsVVJMIjogImh0dHA6Ly8xMjcuMC4w
+LjE6OTI5Mi92MSIsICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo5Mjky
+L3YxIn1dLCAiZW5kcG9pbnRzX2xpbmtzIjogW10sICJ0eXBlIjogImltYWdlIiwg
+Im5hbWUiOiAiZ2xhbmNlIn0sDQoJCQkgICAgIHsiZW5kcG9pbnRzIjogW3siYWRt
+aW5VUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUz
+NDM1ZThhNjBmY2Y4OWJiNjYxN2EiLCAicmVnaW9uIjogInJlZ2lvbk9uZSIsICJp
+bnRlcm5hbFVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjEuMS82NGI2ZjNm
+YmNjNTM0MzVlOGE2MGZjZjg5YmI2NjE3YSIsICJwdWJsaWNVUkwiOiAiaHR0cDov
+LzEyNy4wLjAuMTo4Nzc0L3YxLjEvNjRiNmYzZmJjYzUzNDM1ZThhNjBmY2Y4OWJi
+NjYxN2EifV0sICJlbmRwb2ludHNfbGlua3MiOiBbXSwgInR5cGUiOiAiY29tcHV0
+ZSIsICJuYW1lIjogIm5vdmEifSwNCgkJCSAgICAgeyJlbmRwb2ludHMiOiBbeyJh
+ZG1pblVSTCI6ICJodHRwOi8vMTI3LjAuMC4xOjM1MzU3L3YzIiwgInJlZ2lvbiI6
+ICJSZWdpb25PbmUiLCAiaW50ZXJuYWxVUkwiOiAiaHR0cDovLzEyNy4wLjAuMToz
+NTM1Ny92MyIsICJwdWJsaWNVUkwiOiAiaHR0cDovLzEyNy4wLjAuMTo1MDAwL3Yz
+In1dLCAiZW5kcG9pbnRzX2xpbmtzIjogW10sICJ0eXBlIjogImlkZW50aXR5Iiwg
+Im5hbWUiOiAia2V5c3RvbmUifV0sDQoJICJleHBpcmVzIjogIjIwMTItMDYtMDJU
+MTQ6NDc6MzRaIiwNCgkgInByb2plY3QiOiB7ImVuYWJsZWQiOiB0cnVlLCAiZGVz
+Y3JpcHRpb24iOiBudWxsLCAibmFtZSI6ICJ0ZW5hbnRfbmFtZTEiLCAiaWQiOiAi
+dGVuYW50X2lkMSIsICJkb21haW4iOiB7ImlkIjogImRvbWFpbl9pZDEiLCAibmFt
+ZSI6ICJkb21haW5fbmFtZTEifX0sDQoJICJ1c2VyIjogeyJuYW1lIjogInVzZXJf
+bmFtZTEiLCAiaWQiOiAidXNlcl9pZDEiLCAiZG9tYWluIjogeyJpZCI6ICJkb21h
+aW5faWQxIiwgIm5hbWUiOiAiZG9tYWluX25hbWUxIn19LA0KCSAicm9sZXMiOiBb
+eyJuYW1lIjogInJvbGUxIn0sIHsibmFtZSI6ICJyb2xlMiJ9XQ0KCSB9DQp9DQox
+ggFJMIIBRQIBATCBpDCBnjEKMAgGA1UEBRMBNTELMAkGA1UEBhMCVVMxCzAJBgNV
+BAgTAkNBMRIwEAYDVQQHEwlTdW5ueXZhbGUxEjAQBgNVBAoTCU9wZW5TdGFjazER
+MA8GA1UECxMIS2V5c3RvbmUxJTAjBgkqhkiG9w0BCQEWFmtleXN0b25lQG9wZW5z
+dGFjay5vcmcxFDASBgNVBAMTC1NlbGYgU2lnbmVkAgERMAcGBSsOAwIaMA0GCSqG
+SIb3DQEBAQUABIGAMyJ/o4F6kFPZJ1oGPOaJywv7WKia3x2IOxlDSGBOSfiH64MA
+Im3kv3AUSfVd9S+ulTHHWST9XGD3eWx8dBMVYO/RcFk6+qala2ryrUYhlOWMkFsB
+LCNl0HJoUElEPJuqrwVW7Uy90IE0oGbW5uxsm7qoGBHp1B5z2CikaJBKhgg=
+-----END CMS-----
diff --git a/examples/pki/cms/revocation_list.pem b/examples/pki/cms/revocation_list.pem
index 969d10d..ff22443 100644
--- a/examples/pki/cms/revocation_list.pem
+++ b/examples/pki/cms/revocation_list.pem
@@ -6,7 +6,7 @@ MYIBSTCCAUUCAQEwgaQwgZ4xCjAIBgNVBAUTATUxCzAJBgNVBAYTAlVTMQswCQYD
VQQIEwJDQTESMBAGA1UEBxMJU3Vubnl2YWxlMRIwEAYDVQQKEwlPcGVuU3RhY2sx
ETAPBgNVBAsTCEtleXN0b25lMSUwIwYJKoZIhvcNAQkBFhZrZXlzdG9uZUBvcGVu
c3RhY2sub3JnMRQwEgYDVQQDEwtTZWxmIFNpZ25lZAIBETAHBgUrDgMCGjANBgkq
-hkiG9w0BAQEFAASBgDNDhvViAo8EqTVVvZ00pWUWjajTwoV1w1os1XDJ1XacBUo+
-rsh7gljIIVuvHL2F9C660I5jxhb7QVsTge3CwSiDmexxBAPOs4lNR5hFH7FdT47b
-OK2qd0XnRjo5F7odUxIkozuQ/UISaNTPeWxGEMNVhpTXo2Dwn8wN1wrs/Z2E
+hkiG9w0BAQEFAASBgEqJBkjT4owaIANEzzVTS17GG4VR/s1rQOAajqYCSt+PEsz4
+H1QVsstP/FznwrfrphEdAvosWs3vTx9GgDm1wI5gBeAP56rbtGqzsqZ1PrbzjRpI
+5jHjMF99oMdVeazRCk4CaaoiFo9Rb7A4HfEGHAhoyOieW90Pz3PuLcQqLqSS
-----END CMS-----
diff --git a/examples/pki/gen_pki.sh b/examples/pki/gen_pki.sh
index 9bf6c32..5cea13e 100755
--- a/examples/pki/gen_pki.sh
+++ b/examples/pki/gen_pki.sh
@@ -203,7 +203,7 @@ function check_openssl {
}
function gen_sample_cms {
- for json_file in "${CMS_DIR}/auth_token_revoked.json" "${CMS_DIR}/auth_token_unscoped.json" "${CMS_DIR}/auth_token_scoped.json" "${CMS_DIR}/revocation_list.json"
+ for json_file in "${CMS_DIR}/auth_token_revoked.json" "${CMS_DIR}/auth_token_unscoped.json" "${CMS_DIR}/auth_token_scoped.json" "${CMS_DIR}/revocation_list.json" "${CMS_DIR}/auth_v3_token_scoped.json" "${CMS_DIR}/auth_v3_token_revoked.json"
do
openssl cms -sign -in $json_file -nosmimecap -signer $CERTS_DIR/signing_cert.pem -inkey $PRIVATE_DIR/signing_key.pem -outform PEM -nodetach -nocerts -noattr -out ${json_file/.json/.pem}
done
diff --git a/examples/pki/private/cakey.pem b/examples/pki/private/cakey.pem
index 3db9c22..3513f92 100644
--- a/examples/pki/private/cakey.pem
+++ b/examples/pki/private/cakey.pem
@@ -1,16 +1,16 @@
-----BEGIN PRIVATE KEY-----
-MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMXgnd5wlHApGxZ5
-8LrpEkHU995lT9PxtMgkp0tpFhg7R5HQw9K7TfQk5NHB28hNzf8UE/c0z2pJXggP
-nAzvdx27NQeJGX5CWsi6fITZ8vH/+SxgfxxC+CE/6BkDpzw21MgBtq11vWL7XVax
-NeU12Ax889U66i3CrObuCYt2mbpzAgMBAAECgYEAligxJE9CFSrcR14Zc3zSQeqe
-fcFbpnXQveAyo2MHRTQWx2wobY19RjuI+DOn2IRSQbK2w+zrSLiMBonx3U8Kj8nx
-A4EQ75GLJEEr81TvBoIZSJAqrowNrkXNq8W++qwjlGXRjKiBAYlKMrFvR4lij4XN
-6cdB7kGdSIUmhvC20sECQQD4ebCGfsgFWnrqOrco6T9eQRTvP3+gJuqYXYLuVSTC
-R4gHxT5QVXSZt/Hv3UWJ0BLDbyLzLGHf30w1AqgwsUP5AkEAy96qXq6j2+IRa5w7
-2G+KZHF5N/MK/Hyy27Jw67GBVeGQj1Dwq2ZGAJBZrfXjTtQQAGdQ7EfOTCAOzHgX
-2Bx0ywJAYqfGbBBIkL+VEA0SDh9WNrE2g6u9m7P371kplEGgH7dRDmzFShYz/pin
-aep8IrTHzmsBAHY9wiqh0mZkqzim2QJADTYdxkr89WfeByI1wp3f0wiDeXu3j4sp
-MBGNPcjf/8fBTXhKUGEtUiYImbxggaA+dTg8x0MT/FzreJajvO6DJwJARMc6rhzv
-aTlm4IgApcDPBeuz6SKex9TfvDUJpqACoFM4lMgyHADi9NrJBslxFHPP5eTiM2Ag
-vI7EuW837e6raQ==
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOw4quFzQ/xbUOKu
+LtXdiZLPA0Wi38iGEa+T8tp7j3US44wAamckdZb4cq9/Qx03EBKd2mcJvUoPrLnS
+lnHQMH2VGA1whZpZTWqt8ydQdDYB1SUKeUoxcjq8EKl8X8Sd3dP5amlyFCOIGVhF
+yAXYgaYlmf+s6FIzpY55Uy2zX+nZAgMBAAECgYEAkSGL03InHf/YpTzRJ7Kx2JH5
+d6pHBYNhkFc8yQFLNWnChfynYvFikbvZcnuk92kiJd34FoBEXSFeRNjed9SqRP+i
+gBXy8nqDnnm6af/URHz1H00pbiTAS5xSJZ2XUFCAa0eJEdDv8bEWdTbhfbYc1Lt2
+FISQ1b0hO7gqI1cvoAUCQQD+RFOg1N6eaIiOowQL5YrT8+EywWZqDHAPAYpQvvdP
+UxZtKA7lyiA8fy5bVGc3zmv6D3ZpNKPh5p4WpABvKC+DAkEA7dTaWrsJZr2V1plC
+71JmexyQNJBrCQb3zoJo2oImuAVXPlj3aNhwJftPaZXt6brICBWfDH6CD/YH7rrt
+6HyGcwJAAdrBuWSUExe0F0Y9G1EbSBx5QgODGbbpglKCjcA20Y9LlJQ8N5TX01ki
+H2xoLFIHG5XNSUsm/tjNwmCD2Eu0vQJBAK1XVAaJB+MgDtOoRMbVUegs+1W0ZK7h
+qz+SgQWxkrLRAbNpeHmsNqEYN9sG8a5G+oAZ8iBTHEyxzzpKeBfYms0CQB1EUSoS
+I96Wh4Mae7TXak6aSfl/dF2c3vNB2oYjZTN58JM8l731bh2rI4/0kSPbV5Mtnmk4
+AOLVl+ZJjR6y90I=
-----END PRIVATE KEY-----
diff --git a/examples/pki/private/signing_key.pem b/examples/pki/private/signing_key.pem
index b6ad710..2a21dbb 100644
--- a/examples/pki/private/signing_key.pem
+++ b/examples/pki/private/signing_key.pem
@@ -1,16 +1,16 @@
-----BEGIN PRIVATE KEY-----
-MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALqEAuiAajMQufOO
-XP2ZC7HXEG0x6iKWCRxJMBKMcnhI8IhhX4mZomfdaUhTXPTH43SZzfm3v53VUtfc
-NZ66yb1f7nVbH+iWVpEz1+R8sZ5ZcM5wh6T+4H7zGcxrKs1pUnYg+pnEWwmEV/YQ
-gmrht44QrSEiDQlWDGEBh+kEom/bAgMBAAECgYBywfSUHya4gqsW2toGQps6cauu
-s85uN0glujY0w2tO7Pnpv5errvaI12cG1BvWlAIz5MohwlfIgc919wyavCyRJgQN
-xQo5v5MEMYKKc8ppmXpRr03HLwoPLOHVs6UHRJQT9dhOBfmLzMZIP7P/lJlt2/1X
-Okwxft/PWorczKX1aQJBAORlVqP+Cj4r5kz1A77agnCvINioV1VM5n9PvzPVzYLH
-5r1I53RWFooy1Hx2RUCmtSRQMZMeI9iGMg9c8d3LJ4UCQQDRDuIAd3AoNBcwXKC4
-BPNkbI9BSqnpIdZo87BzpY8rJ/ra3VHMHuq4w+gQsmmEy3pp01AZd1uBqv3s1wHy
-muffAkEAn2ZmiH+lUGy9B5q8qXfBL7naF7utb/gCqnnSvO+LxamUTSjTeKsYgg0l
-pVO503xF0fkyEDYp2FUYHQbGOwAtLQJAHkJ3N/YRx9/yU0+0+63LxQdpnNu/yDzb
-mglbywF1vZtl1fQe+NqowuGoX3JTj6McLuElQOpj1lr3siZU49bEJQJBANRazUzj
-Xfoja7wGuZ3PwHdxxoNDlJ2u0rYjcfK9VZuPGSz/25iCOkaar3OralJ3lfCWbFKA
-vvRp8Hl2Yk4hdKM=
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKYm0YA4g5eLu96H
+lUI5IjQTflWGEkPNlCaEL7qGG82J3QIpPT+t73+NpbtrUjLPFP6DZfxKh7yPdq2Y
+Rqc6/cEVQELmimJaz5NUp/YSRxY46LUh2Eqvor5Ru1pi6CK0JGGMxa96/soomLcy
+9HL+vKG4yhMBjGWrJ/eZyUo6gMRVAgMBAAECgYAmfB9Sn8R7ObaOWMFN0YYGoe1F
+SgS5B8klEsErZxzRgvlaIss5EMTEur6EptsnQagPO8hHo8vE9UX796WF3rgfvYlm
+rWzADFF9JQeb1CRy2wdPEB5wHYWksynKaRhPt6byv2qNqmTKB6JH3fbm1q7Hkrw6
+BjDvuadpdrWBzTPOEQJBANIdSkW2Yo0HVqZz428Ng1zXQQkwlONrFmtHV5OrPLKs
+cu5qE2hGHlu3fxr/Gb/bqLwaCx+LUUjgEopChwQyQU8CQQDKb7vefl8JDRYNcLPM
+CCT7D93g+kYW3ONziBYwQ0sOpSfIS1WQfNRVvsHFAb9IF9g+qgOo4rELEsDeKkUo
+C88bAkAFHruZmUkrgJtG8RoAscaas5AdJjbql8hzEsj6iziube9bCfCxIMxKld0e
+DktVVof1FXlh6mYvrW4mOlrJ6mOXAkAgCoFc3Pmj0BtucykyIRPhXQiMZHCVi87A
+aYjBiNUnc0KRtELNxMRC8hdvXDBvc765ZGWB5KeLDiPSxjP9+6iDAkA+G1v4y4FP
+r8jd6yPPORii1lTAnYAeoBvgqHj7l/2qi+QEYKIW7Q0pNx20BiO3YZoAgL7LoxyR
+EVbW0VXOpHxk
-----END PRIVATE KEY-----
diff --git a/examples/pki/private/ssl_key.pem b/examples/pki/private/ssl_key.pem
index 2e4667f..e8ad2d7 100644
--- a/examples/pki/private/ssl_key.pem
+++ b/examples/pki/private/ssl_key.pem
@@ -1,16 +1,16 @@
-----BEGIN PRIVATE KEY-----
-MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALVu4bjaOH33yAx0
-WdpEqj4UDVsLxVjWxEpIbOlDlc6IfJd+cUriQtxf6ahjxtzLPERS81SnwZmrICWZ
-ngbOn733pULMTZktTJH+o7C74NdKwUSNxjlCeWUy+FqIQoje4ygoJRPpMdkp1wHN
-O0ZERwRN9e8M5TIlx/LRtk+q8bT5AgMBAAECgYAmwq6EYFJrTvE0//JmN/8qzfvg
-dI5PoWpD+F8UInUxr2T2tHOdrOLd07vGVrKYXu7cJeCIOGKa4r02azAggioL/nE9
-FgPpqEC+QROvLuhFsk1gLZ2pGQ06sveKZVMH22h59BKZkYlhjh5qd4vlmhPqkmPp
-gdXj7ZjDCJhhQdFVkQJBANp18k2mVksn8q29LMieVTSIZNN3ucDA1QHbim+3fp/O
-GxCzU7Mv1Xfnu1zoRFu5/sF3YG0Zy3TGPDrEljBC3rUCQQDUnBjVFXL35OkBZqXW
-taJPzGbsPoqAO+Ls2juS97zNzeGxUNhvcKuEvHO63PXqDxp1535DpvJEBN1rT2FF
-iaO1AkEAt/QTWWFUTqrPxY6DNFdm5fpn9E1fg7icZJkKBDJeFJCH59MpCryfovzl
-n0ERtq9ynlQ4RQYwdR8rvkylLvRP9QJAOiXHFOAc5XeR0nREfwiGL9TzgUFJl/DJ
-C4ZULMnctVzNkTVPPItQHal87WppR26CCiUZ/161e6zo8eRv8hjG0QJABWqfYQuK
-dWH8nxlXS+NFUDbsCdL+XpOVE7iEH7hvSw/A/kz40mLx8sDp/Fz1ysrogR/L+NGC
-Vrlwm4q/WYJO0Q==
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM323GVGJ6UImf6n
+fz9P+9MURBo0okaV/3ewyfSMri8DbM0sCqDtC43R1jIrHtEdnUU7kHguFXc09p9p
+HSRbblZ3TNUuZgfoLTNUUY5LETrXdlIQ8WQDqUZq2kSbUBWYkHOYlzmowoWa2hKU
+C1ifHcleI2dVMW+LIkDhXPEc4XO1AgMBAAECgYBOZfMKkaOxjA6iAjvLa7Sdag9q
+MjK6z4nIk4CsF4iN2K3ngyYgj1pgh0kTG5rFWpJssfmR5WjCUWS21RoEptDeZf/A
+jRqzW3u493JAhyOjTK4DYbB9CwCmeGkoImC3nn2PrBgG1OPrSQMB3ODqVA2Pa1eF
+omqKQmAqCCijtmllmQJBAOnpN3sjykUlGVWY7HxdBAOsQ5DkkCXL6ZSjA3pRYvJQ
+12pKELZyxZ8GtVCFvOjaCpdxL+1MsRHkEfZpWz9o9BsCQQDhagjUFbgAQzo/TH1X
+iblrnWUi7rs+IIDOF48qy/t1FKFlyCHbMYQLB/rPSN1G+5uMEapCuOBpVQsO9v5n
+wJRvAkBQXOPG1sEDiH9vvR5ii8J5UJHWEfDES45wlqD3QUbxYXzg85lSVZQ30qIw
+jAIfLeo9pZGFwbeEIgtZ0VCcNH7JAkBK3FEkRjY+eBUvEnMKEGYw9CuzZz9uCZNd
+Xnughe/z2S8kw0tjJVWp6DOGhbdfLI5i/TbjQ8zbjm/Gv4aL5GwnAkEA42UWJKNQ
+ztq73xmVmihToMjMe6k2DDPQpq+e2b/522Vz1ZDJlIV9tpoykFX2XiPnRz1o1oWd
+DXQBvYeFzthvKA==
-----END PRIVATE KEY-----
diff --git a/keystoneclient/middleware/auth_token.py b/keystoneclient/middleware/auth_token.py
index 5d1d33d..084d6f2 100644
--- a/keystoneclient/middleware/auth_token.py
+++ b/keystoneclient/middleware/auth_token.py
@@ -61,35 +61,69 @@ HTTP_X_IDENTITY_STATUS
The underlying service will only see a value of 'Invalid' if the Middleware
is configured to run in 'delay_auth_decision' mode
-HTTP_X_TENANT_ID
- Identity service managed unique identifier, string
+HTTP_X_DOMAIN_ID
+ Identity service managed unique identifier, string. Only present if
+ this is a domain-scoped token.
-HTTP_X_TENANT_NAME
- Unique tenant identifier, string
+HTTP_X_DOMAIN_NAME
+ Unique domain name, string. Only present if this is a domain-scoped token.
+
+HTTP_X_PROJECT_ID
+ Identity service managed unique identifier, string. Only present if
+ this is a project-scoped token.
+
+HTTP_X_PROJECT_NAME
+ Project name, unique within owning domain, string. Only present if
+ this is a project-scoped token.
+
+HTTP_X_PROJECT_DOMAIN_ID
+ Identity service managed unique identifier of owning domain of
+ project, string. Only present if this is a project-scoped token.
+
+HTTP_X_PROJECT_DOMAIN_NAME
+ Name of owning domain of project, string. Only present if this is a
+ project-scoped token.
HTTP_X_USER_ID
Identity-service managed unique identifier, string
HTTP_X_USER_NAME
- Unique user identifier, string
+ User identifier, unique within owning domain, string
+
+HTTP_X_USER_DOMAIN_ID
+ Identity service managed unique identifier of owning domain of user, string
+
+HTTP_X_USER_DOMAIN_NAME
+ Name of owning domain of user, string
HTTP_X_ROLES
- Comma delimited list of case-sensitive Roles
+ Comma delimited list of case-sensitive role names
HTTP_X_SERVICE_CATALOG
json encoded keystone service catalog (optional).
+HTTP_X_TENANT_ID
+ *Deprecated* in favor of HTTP_X_PROJECT_ID
+ Identity service managed unique identifier, string. For v3 tokens, this
+ will be set to the same value as HTTP_X_PROJECT_ID
+
+HTTP_X_TENANT_NAME
+ *Deprecated* in favor of HTTP_X_PROJECT_NAME
+ Project identifier, unique within owning domain, string. For v3 tokens,
+ this will be set to the same value as HTTP_X_PROJECT_NAME
+
HTTP_X_TENANT
*Deprecated* in favor of HTTP_X_TENANT_ID and HTTP_X_TENANT_NAME
- Keystone-assigned unique identifier, deprecated
+ Keystone-assigned unique identifier, string. For v3 tokens, this
+ will be set to the same value as HTTP_X_PROJECT_ID
HTTP_X_USER
*Deprecated* in favor of HTTP_X_USER_ID and HTTP_X_USER_NAME
- Unique user name, string
+ User name, unique within owning domain, string
HTTP_X_ROLE
*Deprecated* in favor of HTTP_X_ROLES
- This is being renamed, and the new header contains the same data.
+ Will contain the same values as HTTP_X_ROLES.
OTHER ENVIRONMENT VARIABLES
---------------------------
@@ -157,8 +191,10 @@ opts = [
cfg.IntOpt('auth_port', default=35357),
cfg.StrOpt('auth_protocol', default='https'),
cfg.StrOpt('auth_uri', default=None),
+ cfg.StrOpt('auth_version', default=None),
cfg.BoolOpt('delay_auth_decision', default=False),
cfg.BoolOpt('http_connect_timeout', default=None),
+ cfg.StrOpt('http_handler', default=None),
cfg.StrOpt('admin_token', secret=True),
cfg.StrOpt('admin_user'),
cfg.StrOpt('admin_password', secret=True),
@@ -171,10 +207,12 @@ opts = [
cfg.ListOpt('memcache_servers'),
cfg.IntOpt('token_cache_time', default=300),
cfg.StrOpt('memcache_security_strategy', default=None),
- cfg.StrOpt('memcache_secret_key', default=None, secret=True),
+ cfg.StrOpt('memcache_secret_key', default=None, secret=True)
]
CONF.register_opts(opts, group='keystone_authtoken')
+LIST_OF_VERSIONS_TO_ATTEMPT = ['v3.0', 'v2.0']
+
def will_expire_soon(expiry):
""" Determines if expiration is about to occur.
@@ -221,10 +259,17 @@ class AuthProtocol(object):
self.auth_host = self._conf_get('auth_host')
self.auth_port = int(self._conf_get('auth_port'))
self.auth_protocol = self._conf_get('auth_protocol')
- if self.auth_protocol == 'http':
- self.http_client_class = httplib.HTTPConnection
+ if not self._conf_get('http_handler'):
+ if self.auth_protocol == 'http':
+ self.http_client_class = httplib.HTTPConnection
+ else:
+ self.http_client_class = httplib.HTTPSConnection
else:
- self.http_client_class = httplib.HTTPSConnection
+ # Really only used for unit testing, since we need to
+ # have a fake handler set up before we issue an http
+ # request to get the list of versions supported by the
+ # server at the end of this initialization
+ self.http_client_class = self._conf_get('http_handler')
self.auth_admin_prefix = self._conf_get('auth_admin_prefix')
self.auth_uri = self._conf_get('auth_uri')
@@ -289,6 +334,9 @@ class AuthProtocol(object):
self.http_connect_timeout = (http_connect_timeout_cfg and
int(http_connect_timeout_cfg))
+ # Determine the highest api version we can use.
+ self.auth_version = self._choose_api_version()
+
def _assert_valid_memcache_protection_config(self):
if self._memcache_security_strategy:
if self._memcache_security_strategy not in ('MAC', 'ENCRYPT'):
@@ -326,6 +374,60 @@ class AuthProtocol(object):
else:
return CONF.keystone_authtoken[name]
+ def _choose_api_version(self):
+ """ Determine the api version that we should use."""
+
+ # If the configuration specifies an auth_version we will just
+ # assume that is correct and use it. We could, of course, check
+ # that this version is supported by the server, but in case
+ # there are some problems in the field, we want as little code
+ # as possible in the way of letting auth_token talk to the
+ # server.
+ if self._conf_get('auth_version'):
+ version_to_use = self._conf_get('auth_version')
+ self.LOG.info('Auth Token proceeding with requested %s apis',
+ version_to_use)
+ else:
+ version_to_use = None
+ versions_supported_by_server = self._get_supported_versions()
+ if versions_supported_by_server:
+ for version in LIST_OF_VERSIONS_TO_ATTEMPT:
+ if version in versions_supported_by_server:
+ version_to_use = version
+ break
+ if version_to_use:
+ self.LOG.info('Auth Token confirmed use of %s apis',
+ version_to_use)
+ else:
+ self.LOG.error(
+ 'Attempted versions [%s] not in list supported by '
+ 'server [%s]',
+ ', '.join(LIST_OF_VERSIONS_TO_ATTEMPT),
+ ', '.join(versions_supported_by_server))
+ raise ServiceError('No compatible apis supported by server')
+ return version_to_use
+
+ def _get_supported_versions(self):
+ versions = []
+ response, data = self._json_request('GET', '/')
+ if response.status != 300:
+ self.LOG.error('Unable to get version info from keystone: %s' %
+ response.status)
+ raise ServiceError('Unable to get version info from keystone')
+ else:
+ try:
+ for version in data['versions']['values']:
+ versions.append(version['id'])
+ except KeyError:
+ self.LOG.error(
+ 'Invalid version response format from server', data)
+ raise ServiceError('Unable to parse version response '
+ 'from keystone')
+
+ self.LOG.debug('Server reports support for api versions: %s',
+ ', '.join(versions))
+ return versions
+
def __call__(self, env, start_response):
"""Handle incoming request.
@@ -371,14 +473,22 @@ class AuthProtocol(object):
"""
auth_headers = (
'X-Identity-Status',
- 'X-Tenant-Id',
- 'X-Tenant-Name',
+ 'X-Domain-Id',
+ 'X-Domain-Name',
+ 'X-Project-Id',
+ 'X-Project-Name',
+ 'X-Project-Domain-Id',
+ 'X-Project-Domain-Name',
'X-User-Id',
'X-User-Name',
+ 'X-User-Domain-Id',
+ 'X-User-Domain-Name',
'X-Roles',
'X-Service-Catalog',
# Deprecated
'X-User',
+ 'X-Tenant-Id',
+ 'X-Tenant-Name',
'X-Tenant',
'X-Role',
)
@@ -459,7 +569,6 @@ class AuthProtocol(object):
"""
conn = self._get_http_connection()
-
try:
conn.request(method, path)
response = conn.getresponse()
@@ -509,7 +618,6 @@ class AuthProtocol(object):
raise ServiceError('Unable to communicate with keystone')
finally:
conn.close()
-
try:
data = jsonutils.loads(body)
except ValueError:
@@ -524,6 +632,10 @@ class AuthProtocol(object):
:return token id upon success
:raises ServerError when unable to communicate with keystone
+ Irrespective of the auth version we are going to use for the
+ user token, for simplicity we always use a v2 admin token to
+ validate the user token.
+
"""
params = {
'auth': {
@@ -588,26 +700,35 @@ class AuthProtocol(object):
Build headers that represent authenticated user:
* X_IDENTITY_STATUS: Confirmed or Invalid
- * X_TENANT_ID: id of tenant if tenant is present
- * X_TENANT_NAME: name of tenant if tenant is present
+ * X_DOMAIN_ID: id of domain, if token is scoped to a domain
+ * X_DOMAIN_NAME: name of domain, if token is scoped to a domain
+ * X_PROJECT_ID: id of project, if token is scoped to a project
+ * X_PROJECT_NAME: name of project, if token is scoped to a project
+ * X_PROJECT_DOMAIN_ID: id of owning domain of project, if
+ token is scoped to a project
+ * X_PROJECT_DOMAIN_NAME: name of owning domain of project, if
+ token is scoped to a project
* X_USER_ID: id of user
* X_USER_NAME: name of user
+ * X_USER_DOMAIN_ID: id of owning domain of user
+ * X_USER_DOMAIN_NAME: name of owning domain of user
* X_ROLES: list of roles
* X_SERVICE_CATALOG: service catalog
- Additional (deprecated) headers include:
+ Additional (deprecated) headers:
* X_USER: name of user
- * X_TENANT: For legacy compatibility before we had ID and Name
+ * X_TENANT_ID: id of tenant (which is equivilent to project),
+ if token is scoped to a project
+ * X_TENANT_NAME: name of tenant (which is equivilent to project),
+ if token is scoped to a project
+ * X_TENANT: For legacy compatibility before we had ID and Name, this
+ is will be the same as X_TENANT_NAME
* X_ROLE: list of roles
:param token_info: token object returned by keystone on authentication
:raise InvalidUserToken when unable to parse token object
"""
- user = token_info['access']['user']
- token = token_info['access']['token']
- roles = ','.join([role['name'] for role in user.get('roles', [])])
-
def get_tenant_info():
"""Returns a (tenant_id, tenant_name) tuple from context."""
def essex():
@@ -619,7 +740,7 @@ class AuthProtocol(object):
return (token['tenantId'], token['tenantId'])
def default_tenant():
- """Assume the user's default tenant."""
+ """Pre-grizzly, assume the user's default tenant."""
return (user['tenantId'], user['tenantName'])
for method in [essex, pre_diablo, default_tenant]:
@@ -630,26 +751,72 @@ class AuthProtocol(object):
raise InvalidUserToken('Unable to determine tenancy.')
- tenant_id, tenant_name = get_tenant_info()
+ # For clarity. set all those attributes that are optional in
+ # either a v2 or v3 token to None first
+ domain_id = None
+ domain_name = None
+ project_id = None
+ project_name = None
+ user_domain_id = None
+ user_domain_name = None
+ project_domain_id = None
+ project_domain_name = None
+
+ if 'access' in token_info:
+ #v2 token
+ user = token_info['access']['user']
+ token = token_info['access']['token']
+ roles = ','.join([role['name'] for role in user.get('roles', [])])
+ catalog_root = token_info['access']
+ catalog_key = 'serviceCatalog'
+ project_id, project_name = get_tenant_info()
+ else:
+ #v3 token
+ token = token_info['token']
+ user = token['user']
+ user_domain_id = user['domain']['id']
+ user_domain_name = user['domain']['name']
+ roles = (','.join([role['name']
+ for role in token.get('roles', [])]))
+ catalog_root = token
+ catalog_key = 'catalog'
+ # For v3, the server will put in the default project if there is
+ # one, so no need for us to add it here (like we do for a v2 token)
+ if 'domain' in token:
+ domain_id = token['domain']['id']
+ domain_name = token['domain']['name']
+ elif 'project' in token:
+ project_id = token['project']['id']
+ project_name = token['project']['name']
+ project_domain_id = token['project']['domain']['id']
+ project_domain_name = token['project']['domain']['name']
user_id = user['id']
user_name = user['name']
rval = {
'X-Identity-Status': 'Confirmed',
- 'X-Tenant-Id': tenant_id,
- 'X-Tenant-Name': tenant_name,
+ 'X-Domain-Id': domain_id,
+ 'X-Domain-Name': domain_name,
+ 'X-Project-Id': project_id,
+ 'X-Project-Name': project_name,
+ 'X-Project-Domain-Id': project_domain_id,
+ 'X-Project-Domain-Name': project_domain_name,
'X-User-Id': user_id,
'X-User-Name': user_name,
+ 'X-User-Domain-Id': user_domain_id,
+ 'X-User-Domain-Name': user_domain_name,
'X-Roles': roles,
# Deprecated
'X-User': user_name,
- 'X-Tenant': tenant_name,
+ 'X-Tenant-Id': project_id,
+ 'X-Tenant-Name': project_name,
+ 'X-Tenant': project_name,
'X-Role': roles,
}
try:
- catalog = token_info['access']['serviceCatalog']
+ catalog = catalog_root[catalog_key]
rval['X-Service-Catalog'] = jsonutils.dumps(catalog)
except KeyError:
pass
@@ -781,11 +948,15 @@ class AuthProtocol(object):
"""
if self._cache and data:
if 'token' in data.get('access', {}):
+ # It's a v2 token
timestamp = data['access']['token']['expires']
- expires = timeutils.parse_isotime(timestamp).strftime('%s')
+ elif 'token' in data:
+ # It's a v3 token
+ timestamp = data['token']['expires']
else:
self.LOG.error('invalid token format')
return
+ expires = timeutils.parse_isotime(timestamp).strftime('%s')
self.LOG.debug('Storing %s token in memcache', token)
self._cache_store(token, data, expires)
@@ -811,12 +982,19 @@ class AuthProtocol(object):
:raise ServiceError if unable to authenticate token
"""
-
- headers = {'X-Auth-Token': self.get_admin_token()}
- response, data = self._json_request(
- 'GET',
- '/v2.0/tokens/%s' % safe_quote(user_token),
- additional_headers=headers)
+ if self.auth_version == 'v3.0':
+ headers = {'X-Auth-Token': self.get_admin_token(),
+ 'X-Subject-Token': safe_quote(user_token)}
+ response, data = self._json_request(
+ 'GET',
+ '/v3/auth/tokens',
+ additional_headers=headers)
+ else:
+ headers = {'X-Auth-Token': self.get_admin_token()}
+ response, data = self._json_request(
+ 'GET',
+ '/v2.0/tokens/%s' % safe_quote(user_token),
+ additional_headers=headers)
if response.status == 200:
self._cache_put(user_token, data)
@@ -910,6 +1088,7 @@ class AuthProtocol(object):
timeout = (self.token_revocation_list_fetched_time +
self.token_revocation_list_cache_timeout)
list_is_current = timeutils.utcnow() < timeout
+
if list_is_current:
# Load the list from disk if required
if not self._token_revocation_list:
diff --git a/tests/test_auth_token_middleware.py b/tests/test_auth_token_middleware.py
index 58be41d..cc730a7 100644
--- a/tests/test_auth_token_middleware.py
+++ b/tests/test_auth_token_middleware.py
@@ -43,11 +43,16 @@ CA = os.path.join(CERTDIR, 'ca.pem')
REVOCATION_LIST = None
REVOKED_TOKEN = None
REVOKED_TOKEN_HASH = None
+REVOKED_v3_TOKEN = None
+REVOKED_v3_TOKEN_HASH = None
SIGNED_REVOCATION_LIST = None
SIGNED_TOKEN_SCOPED = None
SIGNED_TOKEN_UNSCOPED = None
+SIGNED_v3_TOKEN_SCOPED = None
+SIGNED_v3_TOKEN_UNSCOPED = None
SIGNED_TOKEN_SCOPED_KEY = None
SIGNED_TOKEN_UNSCOPED_KEY = None
+SIGNED_v3_TOKEN_SCOPED_KEY = None
VALID_SIGNED_REVOCATION_LIST = None
@@ -55,6 +60,9 @@ UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d"
UUID_TOKEN_NO_SERVICE_CATALOG = '8286720fbe4941e69fa8241723bb02df'
UUID_TOKEN_UNSCOPED = '731f903721c14827be7b2dc912af7776'
VALID_DIABLO_TOKEN = 'b0cf19b55dbb4f20a6ee18e6c6cf1726'
+v3_UUID_TOKEN_DEFAULT = '5603457654b346fdbb93437bfe76f2f1'
+v3_UUID_TOKEN_UNSCOPED = 'd34835fdaec447e695a0a024d84f8d79'
+v3_UUID_TOKEN_DOMAIN_SCOPED = 'e8a7b63aaa4449f38f0c5c05c3581792'
INVALID_SIGNED_TOKEN = string.replace(
"""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
@@ -152,6 +160,79 @@ TOKEN_RESPONSES = {
}
},
},
+ v3_UUID_TOKEN_DEFAULT: {
+ 'token': {
+ 'expires': '2999-01-01T00:00:10Z',
+ 'user': {
+ 'id': 'user_id1',
+ 'name': 'user_name1',
+ 'domain': {
+ 'id': 'domain_id1',
+ 'name': 'domain_name1'
+ }
+ },
+ 'project': {
+ 'id': 'tenant_id1',
+ 'name': 'tenant_name1',
+ 'domain': {
+ 'id': 'domain_id1',
+ 'name': 'domain_name1'
+ }
+ },
+ 'roles': [
+ {'name': 'role1', 'id': 'Role1'},
+ {'name': 'role2', 'id': 'Role2'},
+ ],
+ 'catalog': {}
+ }
+ },
+ v3_UUID_TOKEN_UNSCOPED: {
+ 'token': {
+ 'expires': '2999-01-01T00:00:10Z',
+ 'user': {
+ 'id': 'user_id1',
+ 'name': 'user_name1',
+ 'domain': {
+ 'id': 'domain_id1',
+ 'name': 'domain_name1'
+ }
+ }
+ }
+ },
+ v3_UUID_TOKEN_DOMAIN_SCOPED: {
+ 'token': {
+ 'expires': '2999-01-01T00:00:10Z',
+ 'user': {
+ 'id': 'user_id1',
+ 'name': 'user_name1',
+ 'domain': {
+ 'id': 'domain_id1',
+ 'name': 'domain_name1'
+ }
+ },
+ 'domain': {
+ 'id': 'domain_id1',
+ 'name': 'domain_name1',
+ },
+ 'roles': [
+ {'name': 'role1', 'id': 'Role1'},
+ {'name': 'role2', 'id': 'Role2'},
+ ],
+ 'catalog': {}
+ }
+ }
+}
+
+EXPECTED_V2_DEFAULT_ENV_RESPONSE = {
+ 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
+ 'HTTP_X_TENANT_ID': 'tenant_id1',
+ 'HTTP_X_TENANT_NAME': 'tenant_name1',
+ 'HTTP_X_USER_ID': 'user_id1',
+ 'HTTP_X_USER_NAME': 'user_name1',
+ 'HTTP_X_ROLES': 'role1,role2',
+ 'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat)
+ 'HTTP_X_TENANT': 'tenant_name1', # deprecated (diablo-compat)
+ 'HTTP_X_ROLE': 'role1,role2', # deprecated (diablo-compat)
}
FAKE_RESPONSE_STACK = []
@@ -166,18 +247,25 @@ def setUpModule(self):
self.SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_token_unscoped.pem')) as f:
self.SIGNED_TOKEN_UNSCOPED = cms.cms_to_token(f.read())
+ with open(os.path.join(signing_path, 'auth_v3_token_scoped.pem')) as f:
+ self.SIGNED_v3_TOKEN_SCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_token_revoked.pem')) as f:
self.REVOKED_TOKEN = cms.cms_to_token(f.read())
self.REVOKED_TOKEN_HASH = utils.hash_signed_token(self.REVOKED_TOKEN)
+ with open(os.path.join(signing_path, 'auth_v3_token_revoked.pem')) as f:
+ self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read())
+ self.REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(self.REVOKED_v3_TOKEN)
with open(os.path.join(signing_path, 'revocation_list.json')) as f:
self.REVOCATION_LIST = jsonutils.loads(f.read())
with open(os.path.join(signing_path, 'revocation_list.pem')) as f:
self.VALID_SIGNED_REVOCATION_LIST = jsonutils.dumps(
{'signed': f.read()})
- self.SIGNED_TOKEN_SCOPED_KEY =\
- cms.cms_hash_token(self.SIGNED_TOKEN_SCOPED)
- self.SIGNED_TOKEN_UNSCOPED_KEY =\
- cms.cms_hash_token(self.SIGNED_TOKEN_UNSCOPED)
+ self.SIGNED_TOKEN_SCOPED_KEY = (
+ cms.cms_hash_token(self.SIGNED_TOKEN_SCOPED))
+ self.SIGNED_TOKEN_UNSCOPED_KEY = (
+ cms.cms_hash_token(self.SIGNED_TOKEN_UNSCOPED))
+ self.SIGNED_v3_TOKEN_SCOPED_KEY = (
+ cms.cms_hash_token(self.SIGNED_v3_TOKEN_SCOPED))
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY] = {
'access': {
@@ -213,23 +301,80 @@ def setUpModule(self):
},
},
+ self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY] = {
+ 'token': {
+ 'expires': '2999-01-01T00:00:10Z',
+ 'user': {
+ 'id': 'user_id1',
+ 'name': 'user_name1',
+ 'domain': {
+ 'id': 'domain_id1',
+ 'name': 'domain_name1'
+ }
+ },
+ 'project': {
+ 'id': 'tenant_id1',
+ 'name': 'tenant_name1',
+ 'domain': {
+ 'id': 'domain_id1',
+ 'name': 'domain_name1'
+ }
+ },
+ 'roles': [
+ {'name': 'role1'},
+ {'name': 'role2'}
+ ],
+ 'catalog': {}
+ }
+ }
+
+VERSION_LIST_v3 = {
+ "versions": {
+ "values": [
+ {
+ "id": "v3.0",
+ "status": "stable",
+ "updated": "2013-03-06T00:00:00Z",
+ "links": []
+ },
+ {
+ "id": "v2.0",
+ "status": "beta",
+ "updated": "2011-11-19T00:00:00Z",
+ "links": []
+ }
+ ]
+ }
+}
+
+VERSION_LIST_v2 = {
+ "versions": {
+ "values": [
+ {
+ "id": "v2.0",
+ "status": "beta",
+ "updated": "2011-11-19T00:00:00Z",
+ "links": []
+ }
+ ]
+ }
+}
+
class FakeMemcache(object):
def __init__(self):
self.set_key = None
self.set_value = None
self.token_expiration = None
+ self.token_key = SIGNED_TOKEN_SCOPED_KEY
def get(self, key):
- data = TOKEN_RESPONSES[SIGNED_TOKEN_SCOPED_KEY].copy()
- if not data or key != "tokens/%s" % (data['access']['token']['id']):
+ data = TOKEN_RESPONSES[self.token_key].copy()
+ if not data or key != "tokens/%s" % self.token_key:
return
if not self.token_expiration:
dt = datetime.datetime.now() + datetime.timedelta(minutes=5)
self.token_expiration = dt.strftime("%s")
- dt = datetime.datetime.now() + datetime.timedelta(hours=24)
- ks_expires = dt.isoformat()
- data['access']['token']['expires'] = ks_expires
return (data, str(self.token_expiration))
def set(self, key, value, time=None):
@@ -237,22 +382,26 @@ class FakeMemcache(object):
self.set_key = key
+class v3FakeMemcache(FakeMemcache):
+ def __init__(self):
+ super(v3FakeMemcache, self).__init__()
+ self.token_key = SIGNED_v3_TOKEN_SCOPED_KEY
+
+
class FakeSwiftMemcacheRing(object):
def __init__(self):
self.set_key = None
self.set_value = None
self.token_expiration = None
+ self.token_key = SIGNED_TOKEN_SCOPED_KEY
def get(self, key):
- data = TOKEN_RESPONSES[SIGNED_TOKEN_SCOPED_KEY].copy()
- if not data or key != "tokens/%s" % (data['access']['token']['id']):
+ data = TOKEN_RESPONSES[self.token_key].copy()
+ if not data or key != "tokens/%s" % self.token_key:
return
if not self.token_expiration:
dt = datetime.datetime.now() + datetime.timedelta(minutes=5)
self.token_expiration = dt.strftime("%s")
- dt = datetime.datetime.now() + datetime.timedelta(hours=24)
- ks_expires = dt.isoformat()
- data['access']['token']['expires'] = ks_expires
return (data, str(self.token_expiration))
def set(self, key, value, serialize=True, timeout=0):
@@ -260,6 +409,12 @@ class FakeSwiftMemcacheRing(object):
self.set_key = key
+class v3FakeSwiftMemcacheRing(FakeSwiftMemcacheRing):
+ def __init__(self):
+ super(v3FakeSwiftMemcacheRing, self).__init__()
+ self.token_key = SIGNED_v3_TOKEN_SCOPED_KEY
+
+
class FakeHTTPResponse(object):
def __init__(self, status, body):
self.status = status
@@ -269,86 +424,129 @@ class FakeHTTPResponse(object):
return self.body
-class FakeStackHTTPConnection(object):
-
- def __init__(self, *args, **kwargs):
- pass
+class BaseFakeHTTPConnection(object):
- def getresponse(self):
- if len(FAKE_RESPONSE_STACK):
- return FAKE_RESPONSE_STACK.pop()
- return FakeHTTPResponse(500, jsonutils.dumps('UNEXPECTED RESPONSE'))
+ def _user_token_responses(self, token_id):
+ """ Emulate user token responses.
- def request(self, *_args, **_kwargs):
- pass
-
- def close(self):
- pass
+ Return success if the token is in the list we know
+ about. If the request is for revoked tokens, then return
+ the revoked list, else if a different token is provided,
+ return 404 indicating an unknown (therefore unauthorized) token.
+ """
+ if token_id in TOKEN_RESPONSES.keys():
+ status = 200
+ body = jsonutils.dumps(TOKEN_RESPONSES[token_id])
+ elif token_id == "revoked":
+ status = 200
+ body = SIGNED_REVOCATION_LIST
+ else:
+ status = 404
+ body = str()
+ return status, body
+
+ def fake_v2_responses(self, path):
+ token_id = path.rsplit('/', 1)[1]
+ return self._user_token_responses(token_id)
+
+ def fake_v3_responses(self, path, **kwargs):
+ headers = kwargs.get('headers')
+ token_id = headers['X-Subject-Token']
+ return self._user_token_responses(token_id)
+
+ def fake_v2_admin_token(self, path):
+ status = 200
+ body = jsonutils.dumps({
+ 'access': {
+ 'token': {'id': 'admin_token2',
+ 'expires': '2012-10-03T16:58:01Z'}
+ },
+ })
+ return status, body
-class FakeHTTPConnection(object):
- last_requested_url = ''
+class FakeHTTPConnection(BaseFakeHTTPConnection):
+ """ Emulate a fake Keystone v2 server """
def __init__(self, *args, **kwargs):
self.send_valid_revocation_list = True
+ self.resp = None
def request(self, method, path, **kwargs):
"""Fakes out several http responses.
- If a POST request is made, we assume the calling code is trying
- to get a new admin token.
+ Support the following requests:
- If a GET request is made to validate a token, return success
- if the token is 'token1'. If a different token is provided, return
- a 404, indicating an unknown (therefore unauthorized) token.
+ - Create admin token ('POST /testadmin/v2.0/tokens')
+ - Get versions ('GET /testadmin/')
+ - Get v2 user token responses (see fake_v2_responses)
"""
FakeHTTPConnection.last_requested_url = path
- if method == 'POST':
- status = 200
- body = jsonutils.dumps({
- 'access': {
- 'token': {'id': 'admin_token2'},
- },
- })
-
+ if method == 'POST' and path == '/testadmin/v2.0/tokens':
+ status, body = self.fake_v2_admin_token(path)
else:
- token_id = path.rsplit('/', 1)[1]
- if token_id in TOKEN_RESPONSES.keys():
- status = 200
- body = jsonutils.dumps(TOKEN_RESPONSES[token_id])
- elif token_id == "revoked":
- status = 200
- body = SIGNED_REVOCATION_LIST
+ if path == '/testadmin/':
+ # It's a GET versions call
+ status = 300
+ body = jsonutils.dumps(VERSION_LIST_v2)
else:
- status = 404
- body = str()
+ status, body = self.fake_v2_responses(path)
self.resp = FakeHTTPResponse(status, body)
def getresponse(self):
- return self.resp
+ # If self.resp is set then this is just the response to
+ # the earlier request. If it is not set, then we expect
+ # a stack of responses to have been pre-prepared
+ if self.resp:
+ return self.resp
+ else:
+ if len(FAKE_RESPONSE_STACK):
+ return FAKE_RESPONSE_STACK.pop()
+ return FakeHTTPResponse(
+ 500, jsonutils.dumps('UNEXPECTED RESPONSE'))
def close(self):
pass
+class v3FakeHTTPConnection(FakeHTTPConnection):
+ """ Emulate a fake Keystone v3 server """
+
+ def request(self, method, path, **kwargs):
+ """Fakes out several http responses.
+
+ Support the following requests:
+
+ - Create admin token ('POST /testadmin/v2.0/tokens')
+ - Get versions ('GET /testadmin/')
+ - Get v2 user token responses (see fake_v2_responses)
+ - Get v3 user token responses (see fake_v3_responses)
+
+ """
+ v3FakeHTTPConnection.last_requested_url = path
+ if method == 'POST' and path == '/testadmin/v2.0/tokens':
+ status, body = self.fake_v2_admin_token(path)
+ else:
+ if path == '/testadmin/':
+ # It's a GET versions call
+ status = 300
+ body = jsonutils.dumps(VERSION_LIST_v3)
+ elif path.split('/')[2] == 'v2.0':
+ status, body = self.fake_v2_responses(path)
+ else:
+ status, body = self.fake_v3_responses(path, **kwargs)
+
+ self.resp = FakeHTTPResponse(status, body)
+
+
class FakeApp(object):
"""This represents a WSGI app protected by the auth_token middleware."""
def __init__(self, expected_env=None):
expected_env = expected_env or {}
- self.expected_env = {
- 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
- 'HTTP_X_TENANT_ID': 'tenant_id1',
- 'HTTP_X_TENANT_NAME': 'tenant_name1',
- 'HTTP_X_USER_ID': 'user_id1',
- 'HTTP_X_USER_NAME': 'user_name1',
- 'HTTP_X_ROLES': 'role1,role2',
- 'HTTP_X_USER': 'user_name1', # deprecated (diablo-compat)
- 'HTTP_X_TENANT': 'tenant_name1', # deprecated (diablo-compat)
- 'HTTP_X_ROLE': 'role1,role2', # deprecated (diablo-compat)
- }
+ self.expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE)
self.expected_env.update(expected_env)
def __call__(self, env, start_response):
@@ -360,36 +558,121 @@ class FakeApp(object):
return resp(env, start_response)
+class v3FakeApp(object):
+ """This represents a v3 WSGI app protected by the auth_token middleware."""
+ def __init__(self, expected_env=None):
+ expected_env = expected_env or {}
+ # We should always get back the same v2 items
+ self.expected_env = dict(EXPECTED_V2_DEFAULT_ENV_RESPONSE)
+ # ...and with v3 additions, these are for the DEFAULT TOKEN
+ v3_default_env_additions = {
+ 'HTTP_X_PROJECT_ID': 'tenant_id1',
+ 'HTTP_X_PROJECT_NAME': 'tenant_name1',
+ 'HTTP_X_PROJECT_DOMAIN_ID': 'domain_id1',
+ 'HTTP_X_PROJECT_DOMAIN_NAME': 'domain_name1',
+ 'HTTP_X_USER_DOMAIN_ID': 'domain_id1',
+ 'HTTP_X_USER_DOMAIN_NAME': 'domain_name1'
+ }
+ self.expected_env.update(v3_default_env_additions)
+ # And finally update for anything passed in
+ self.expected_env.update(expected_env)
+
+ def __call__(self, env, start_response):
+ for k, v in self.expected_env.items():
+ assert env[k] == v, '%s != %s' % (env[k], v)
+ resp = webob.Response()
+ resp.body = 'SUCCESS'
+ return resp(env, start_response)
+
+
class BaseAuthTokenMiddlewareTest(testtools.TestCase):
+ """ Base test class for auth_token middleware.
+
+ All the tests allow for running with auth_token
+ configured for receiving v2 or v3 tokens, with the
+ choice being made by passing configuration data into
+ Setup().
- def setUp(self, expected_env=None):
+ The base class will, by default, run all the tests
+ expecting v2 token formats. Child classes can override
+ this to specify, for instance, v3 format.
+
+ """
+ def setUp(self, expected_env=None, auth_version=None,
+ fake_app=None, fake_http=None, token_dict=None,
+ fake_memcache=None, fake_memcache_ring=None):
testtools.TestCase.setUp(self)
expected_env = expected_env or {}
- conf = {
- 'admin_token': 'admin_token1',
+ if token_dict:
+ self.token_dict = token_dict
+ else:
+ self.token_dict = {
+ 'uuid_token_default': UUID_TOKEN_DEFAULT,
+ 'uuid_token_unscoped': UUID_TOKEN_UNSCOPED,
+ 'signed_token_scoped': SIGNED_TOKEN_SCOPED,
+ 'revoked_token': REVOKED_TOKEN,
+ 'revoked_token_hash': REVOKED_TOKEN_HASH
+ }
+
+ self.conf = {
'auth_host': 'keystone.example.com',
'auth_port': 1234,
'auth_admin_prefix': '/testadmin',
'signing_dir': CERTDIR,
+ 'auth_version': auth_version
}
- self.middleware = auth_token.AuthProtocol(FakeApp(expected_env), conf)
- self.middleware.http_client_class = FakeHTTPConnection
- self.middleware._iso8601 = iso8601
+ # Base assumes v2 for fake app and http, can be overridden for
+ # child classes by called set_middleware() directly
+ self.fake_app = fake_app or FakeApp
+ self.fake_http = fake_http or FakeHTTPConnection
+ self.set_middleware(self.fake_app, self.fake_http,
+ expected_env, self.conf)
self.response_status = None
self.response_headers = None
+
+ self.fake_memcache = fake_memcache or FakeMemcache
+ self.fake_memcache_ring = fake_memcache_ring or FakeSwiftMemcacheRing
+
+ signed_list = 'SIGNED_REVOCATION_LIST'
+ valid_signed_list = 'VALID_SIGNED_REVOCATION_LIST'
+ globals()[signed_list] = globals()[valid_signed_list]
+
+ def set_fake_http(self, http_handler):
+ """ Configure the http handler for the auth_token middleware.
+
+ Allows tests to override the default handler on specific tests,
+ e.g. to use v2 for those parts of auth_token that still use v2
+ tokens while running the v3 test class, i.e. getting an admin
+ token or revocation list.
+
+ """
+ self.middleware.http_client_class = http_handler
+
+ def set_middleware(self, fake_app=None, fake_http=None,
+ expected_env=None, conf=None):
+ """ Configure the class ready to call the auth_token middleware.
+
+ Set up the various fake items needed to run the middleware.
+ Individual tests that need to further refine these can call this
+ function to override the class defaults.
+
+ """
+ conf = conf or self.conf
+ if 'http_handler' not in conf:
+ fake_http = fake_http or self.fake_http
+ conf['http_handler'] = fake_http
+ fake_app = fake_app or self.fake_app
+ self.middleware = auth_token.AuthProtocol(fake_app(expected_env), conf)
+ self.middleware._iso8601 = iso8601
self.middleware.revoked_file_name = tempfile.mkstemp()[1]
cache_timeout = datetime.timedelta(days=1)
self.middleware.token_revocation_list_cache_timeout = cache_timeout
self.middleware.token_revocation_list = jsonutils.dumps(
{"revoked": [], "extra": "success"})
- signed_list = 'SIGNED_REVOCATION_LIST'
- valid_signed_list = 'VALID_SIGNED_REVOCATION_LIST'
- globals()[signed_list] = globals()[valid_signed_list]
-
def tearDown(self):
testtools.TestCase.tearDown(self)
try:
@@ -449,9 +732,8 @@ class StackResponseAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest):
"""
- def setUp(self, expected_env=None):
- super(StackResponseAuthTokenMiddlewareTest, self).setUp(expected_env)
- self.middleware.http_client_class = FakeStackHTTPConnection
+ def setUp(self):
+ super(StackResponseAuthTokenMiddlewareTest, self).setUp()
def test_fetch_revocation_list_with_expire(self):
# first response to revocation list should return 401 Unauthorized
@@ -493,7 +775,8 @@ class DiabloAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest):
# now deprecated (diablo-compat)
'HTTP_X_TENANT': 'tenant_id1',
}
- super(DiabloAuthTokenMiddlewareTest, self).setUp(expected_env)
+ super(DiabloAuthTokenMiddlewareTest, self).setUp(
+ expected_env=expected_env)
def test_valid_diablo_response(self):
req = webob.Request.blank('/')
@@ -504,72 +787,47 @@ class DiabloAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest):
class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
- def assert_valid_request_200(self, token):
+
+ def assert_valid_last_url(self, token_id):
+ # Default version (v2) has id in the token, override this
+ # method for v3 and other versions
+ self.assertEqual("/testadmin/v2.0/tokens/%s" % token_id,
+ self.middleware.http_client_class.last_requested_url)
+
+ def assert_valid_request_200(self, token, with_catalog=True):
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = token
body = self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.response_status, 200)
- self.assertTrue(req.headers.get('X-Service-Catalog'))
+ if with_catalog:
+ self.assertTrue(req.headers.get('X-Service-Catalog'))
self.assertEqual(body, ['SUCCESS'])
self.assertTrue('keystone.token_info' in req.environ)
def test_valid_uuid_request(self):
- self.assert_valid_request_200(UUID_TOKEN_DEFAULT)
- self.assertEqual("/testadmin/v2.0/tokens/%s" % UUID_TOKEN_DEFAULT,
- FakeHTTPConnection.last_requested_url)
+ self.assert_valid_request_200(self.token_dict['uuid_token_default'])
+ self.assert_valid_last_url(self.token_dict['uuid_token_default'])
def test_valid_signed_request(self):
- FakeHTTPConnection.last_requested_url = ''
- self.assert_valid_request_200(SIGNED_TOKEN_SCOPED)
+ self.middleware.http_client_class.last_requested_url = ''
+ self.assert_valid_request_200(
+ self.token_dict['signed_token_scoped'])
self.assertEqual(self.middleware.conf['auth_admin_prefix'],
"/testadmin")
#ensure that signed requests do not generate HTTP traffic
- self.assertEqual('', FakeHTTPConnection.last_requested_url)
-
- def assert_unscoped_default_tenant_auto_scopes(self, token):
- """Unscoped requests with a default tenant should "auto-scope."
-
- The implied scope is the user's tenant ID.
-
- """
- req = webob.Request.blank('/')
- req.headers['X-Auth-Token'] = token
- body = self.middleware(req.environ, self.start_fake_response)
- self.assertEqual(self.response_status, 200)
- self.assertEqual(body, ['SUCCESS'])
- self.assertTrue('keystone.token_info' in req.environ)
-
- def test_default_tenant_uuid_token(self):
- self.assert_unscoped_default_tenant_auto_scopes(UUID_TOKEN_DEFAULT)
-
- def test_default_tenant_signed_token(self):
- self.assert_unscoped_default_tenant_auto_scopes(SIGNED_TOKEN_SCOPED)
-
- def assert_unscoped_token_receives_401(self, token):
- """Unscoped requests with no default tenant ID should be rejected."""
- req = webob.Request.blank('/')
- req.headers['X-Auth-Token'] = token
- self.middleware(req.environ, self.start_fake_response)
- self.assertEqual(self.response_status, 401)
- self.assertEqual(self.response_headers['WWW-Authenticate'],
- 'Keystone uri=\'https://keystone.example.com:1234\'')
-
- def test_unscoped_uuid_token_receives_401(self):
- self.assert_unscoped_token_receives_401(UUID_TOKEN_UNSCOPED)
-
- def test_unscoped_pki_token_receives_401(self):
- self.assert_unscoped_token_receives_401(SIGNED_TOKEN_UNSCOPED)
+ self.assertEqual(
+ '', self.middleware.http_client_class.last_requested_url)
def test_revoked_token_receives_401(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
req = webob.Request.blank('/')
- req.headers['X-Auth-Token'] = REVOKED_TOKEN
+ req.headers['X-Auth-Token'] = self.token_dict['revoked_token']
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.response_status, 401)
def get_revocation_list_json(self, token_ids=None):
if token_ids is None:
- token_ids = [REVOKED_TOKEN_HASH]
+ token_ids = [self.token_dict['revoked_token_hash']]
revocation_list = {'revoked': [{'id': x, 'expires': timeutils.utcnow()}
for x in token_ids]}
return jsonutils.dumps(revocation_list)
@@ -578,22 +836,26 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
#explicitly setting an empty revocation list here to document intent
self.middleware.token_revocation_list = jsonutils.dumps(
{"revoked": [], "extra": "success"})
- result = self.middleware.is_signed_token_revoked(REVOKED_TOKEN)
+ result = self.middleware.is_signed_token_revoked(
+ self.token_dict['revoked_token'])
self.assertFalse(result)
def test_is_signed_token_revoked_returns_true(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
- result = self.middleware.is_signed_token_revoked(REVOKED_TOKEN)
+ result = self.middleware.is_signed_token_revoked(
+ self.token_dict['revoked_token'])
self.assertTrue(result)
def test_verify_signed_token_raises_exception_for_revoked_token(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
self.assertRaises(auth_token.InvalidUserToken,
- self.middleware.verify_signed_token, REVOKED_TOKEN)
+ self.middleware.verify_signed_token,
+ self.token_dict['revoked_token'])
def test_verify_signed_token_succeeds_for_unrevoked_token(self):
self.middleware.token_revocation_list = self.get_revocation_list_json()
- self.middleware.verify_signed_token(SIGNED_TOKEN_SCOPED)
+ self.middleware.verify_signed_token(
+ self.token_dict['signed_token_scoped'])
def test_cert_file_missing(self):
self.assertFalse(self.middleware.cert_file_missing(
@@ -622,6 +884,9 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
expected)
def test_get_revocation_list_returns_fetched_list(self):
+ # auth_token uses v2 to fetch this, so don't allow the v3
+ # tests to override the fake http connection
+ self.set_fake_http(FakeHTTPConnection)
self.middleware.token_revocation_list_fetched_time = None
os.remove(self.middleware.revoked_file_name)
self.assertEqual(self.middleware.token_revocation_list,
@@ -642,6 +907,9 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
self.middleware.fetch_revocation_list)
def test_fetch_revocation_list(self):
+ # auth_token uses v2 to fetch this, so don't allow the v3
+ # tests to override the fake http connection
+ self.set_fake_http(FakeHTTPConnection)
fetched_list = jsonutils.loads(self.middleware.fetch_revocation_list())
self.assertEqual(fetched_list, REVOCATION_LIST)
@@ -651,7 +919,7 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.response_status, 401)
self.assertEqual(self.response_headers['WWW-Authenticate'],
- 'Keystone uri=\'https://keystone.example.com:1234\'')
+ "Keystone uri='https://keystone.example.com:1234'")
def test_request_invalid_signed_token(self):
req = webob.Request.blank('/')
@@ -659,14 +927,14 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.response_status, 401)
self.assertEqual(self.response_headers['WWW-Authenticate'],
- 'Keystone uri=\'https://keystone.example.com:1234\'')
+ "Keystone uri='https://keystone.example.com:1234'")
def test_request_no_token(self):
req = webob.Request.blank('/')
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.response_status, 401)
self.assertEqual(self.response_headers['WWW-Authenticate'],
- 'Keystone uri=\'https://keystone.example.com:1234\'')
+ "Keystone uri='https://keystone.example.com:1234'")
def test_request_no_token_log_message(self):
class FakeLog(object):
@@ -693,12 +961,13 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.response_status, 401)
self.assertEqual(self.response_headers['WWW-Authenticate'],
- 'Keystone uri=\'https://keystone.example.com:1234\'')
+ "Keystone uri='https://keystone.example.com:1234'")
def test_memcache(self):
req = webob.Request.blank('/')
- req.headers['X-Auth-Token'] = SIGNED_TOKEN_SCOPED
- self.middleware._cache = FakeMemcache()
+ req.headers['X-Auth-Token'] = (
+ self.token_dict['signed_token_scoped'])
+ self.middleware._cache = self.fake_memcache()
self.middleware._use_keystone_cache = True
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.middleware._cache.set_value, None)
@@ -706,15 +975,16 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
def test_memcache_set_invalid(self):
req = webob.Request.blank('/')
req.headers['X-Auth-Token'] = 'invalid-token'
- self.middleware._cache = FakeMemcache()
+ self.middleware._cache = self.fake_memcache()
self.middleware._use_keystone_cache = True
self.middleware(req.environ, self.start_fake_response)
self.assertEqual(self.middleware._cache.set_value, "invalid")
def test_memcache_set_expired(self):
req = webob.Request.blank('/')
- req.headers['X-Auth-Token'] = SIGNED_TOKEN_SCOPED
- self.middleware._cache = FakeMemcache()
+ req.headers['X-Auth-Token'] = (
+ self.token_dict['signed_token_scoped'])
+ self.middleware._cache = self.fake_memcache()
self.middleware._use_keystone_cache = True
expired = datetime.datetime.now() - datetime.timedelta(minutes=1)
self.middleware._cache.token_expiration = float(expired.strftime("%s"))
@@ -723,8 +993,9 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
def test_swift_memcache_set_expired(self):
req = webob.Request.blank('/')
- req.headers['X-Auth-Token'] = SIGNED_TOKEN_SCOPED
- self.middleware._cache = FakeSwiftMemcacheRing()
+ req.headers['X-Auth-Token'] = (
+ self.token_dict['signed_token_scoped'])
+ self.middleware._cache = self.fake_memcache_ring()
self.middleware._use_keystone_cache = False
expired = datetime.datetime.now() - datetime.timedelta(minutes=1)
self.middleware._cache.token_expiration = float(expired.strftime("%s"))
@@ -735,48 +1006,38 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
self.disable_module('memcache')
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
- 'memcache_servers': 'localhost:11211',
+ 'auth_admin_prefix': '/testadmin',
+ 'memcache_servers': 'localhost:11211'
}
-
- auth_token.AuthProtocol(FakeApp(), conf)
+ self.set_middleware(conf=conf)
def test_use_cache_from_env(self):
env = {'swift.cache': 'CACHE_TEST'}
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'cache': 'swift.cache',
- 'memcache_servers': 'localhost:11211',
+ 'memcache_servers': 'localhost:11211'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
- auth._init_cache(env)
- self.assertEqual(auth._cache, 'CACHE_TEST')
+ self.set_middleware(conf=conf)
+ self.middleware._init_cache(env)
+ self.assertEqual(self.middleware._cache, 'CACHE_TEST')
def test_not_use_cache_from_env(self):
self.disable_module('memcache')
env = {'swift.cache': 'CACHE_TEST'}
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
- 'memcache_servers': 'localhost:11211',
+ 'auth_admin_prefix': '/testadmin',
+ 'memcache_servers': 'localhost:11211'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
- auth._init_cache(env)
- self.assertEqual(auth._cache, None)
-
- def test_request_prevent_service_catalog_injection(self):
- req = webob.Request.blank('/')
- req.headers['X-Service-Catalog'] = '[]'
- req.headers['X-Auth-Token'] = UUID_TOKEN_NO_SERVICE_CATALOG
- body = self.middleware(req.environ, self.start_fake_response)
- self.assertEqual(self.response_status, 200)
- self.assertFalse(req.headers.get('X-Service-Catalog'))
- self.assertEqual(body, ['SUCCESS'])
+ self.set_middleware(conf=conf)
+ self.middleware._init_cache(env)
+ self.assertEqual(self.middleware._cache, None)
def test_will_expire_soon(self):
tenseconds = datetime.datetime.utcnow() + datetime.timedelta(
@@ -788,152 +1049,328 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
def test_encrypt_cache_data(self):
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
'memcache_security_strategy': 'encrypt',
- 'memcache_secret_key': 'mysecret',
+ 'memcache_secret_key': 'mysecret'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
- encrypted_data = \
- auth._protect_cache_value('token',
- TOKEN_RESPONSES[UUID_TOKEN_DEFAULT])
+ self.set_middleware(conf=conf)
+ encrypted_data = self.middleware._protect_cache_value(
+ 'token', TOKEN_RESPONSES[self.token_dict['uuid_token_default']])
self.assertEqual('{ENCRYPT:AES256}', encrypted_data[:16])
self.assertEqual(
- TOKEN_RESPONSES[UUID_TOKEN_DEFAULT],
- auth._unprotect_cache_value('token', encrypted_data))
+ TOKEN_RESPONSES[self.token_dict['uuid_token_default']],
+ self.middleware._unprotect_cache_value('token', encrypted_data))
# should return None if unable to decrypt
self.assertIsNone(
- auth._unprotect_cache_value('token', '{ENCRYPT:AES256}corrupted'))
+ self.middleware._unprotect_cache_value(
+ 'token', '{ENCRYPT:AES256}corrupted'))
self.assertIsNone(
- auth._unprotect_cache_value('mykey', encrypted_data))
+ self.middleware._unprotect_cache_value('mykey', encrypted_data))
def test_sign_cache_data(self):
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
'memcache_security_strategy': 'mac',
- 'memcache_secret_key': 'mysecret',
+ 'memcache_secret_key': 'mysecret'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
- signed_data = \
- auth._protect_cache_value('mykey',
- TOKEN_RESPONSES[UUID_TOKEN_DEFAULT])
+ self.set_middleware(conf=conf)
+ signed_data = self.middleware._protect_cache_value(
+ 'mykey', TOKEN_RESPONSES[self.token_dict['uuid_token_default']])
expected = '{MAC:SHA1}'
self.assertEqual(
signed_data[:10],
expected)
self.assertEqual(
- TOKEN_RESPONSES[UUID_TOKEN_DEFAULT],
- auth._unprotect_cache_value('mykey', signed_data))
+ TOKEN_RESPONSES[self.token_dict['uuid_token_default']],
+ self.middleware._unprotect_cache_value('mykey', signed_data))
# should return None on corrupted data
self.assertIsNone(
- auth._unprotect_cache_value('mykey', '{MAC:SHA1}corrupted'))
+ self.middleware._unprotect_cache_value('mykey',
+ '{MAC:SHA1}corrupted'))
def test_no_memcache_protection(self):
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
- 'memcache_secret_key': 'mysecret',
+ 'memcache_secret_key': 'mysecret'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
- data = auth._protect_cache_value('mykey', 'This is a test!')
+ self.set_middleware(conf=conf)
+ data = self.middleware._protect_cache_value('mykey',
+ 'This is a test!')
self.assertEqual(data, 'This is a test!')
self.assertEqual(
'This is a test!',
- auth._unprotect_cache_value('mykey', data))
+ self.middleware._unprotect_cache_value('mykey', data))
def test_get_cache_key(self):
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
- 'memcache_secret_key': 'mysecret',
+ 'memcache_secret_key': 'mysecret'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
+ self.set_middleware(conf=conf)
self.assertEqual(
'tokens/mytoken',
- auth._get_cache_key('mytoken'))
+ self.middleware._get_cache_key('mytoken'))
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
'memcache_security_strategy': 'mac',
- 'memcache_secret_key': 'mysecret',
+ 'memcache_secret_key': 'mysecret'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
+ self.set_middleware(conf=conf)
expected = 'tokens/' + memcache_crypt.hash_data('mytoken' + 'mysecret')
- self.assertEqual(auth._get_cache_key('mytoken'), expected)
+ self.assertEqual(self.middleware._get_cache_key('mytoken'), expected)
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
'memcache_security_strategy': 'Encrypt',
- 'memcache_secret_key': 'abc!',
+ 'memcache_secret_key': 'abc!'
}
- auth = auth_token.AuthProtocol(FakeApp(), conf)
+ self.set_middleware(conf=conf)
expected = 'tokens/' + memcache_crypt.hash_data('mytoken' + 'abc!')
- self.assertEqual(auth._get_cache_key('mytoken'), expected)
+ self.assertEqual(self.middleware._get_cache_key('mytoken'), expected)
def test_assert_valid_memcache_protection_config(self):
# test missing memcache_secret_key
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
- 'memcache_security_strategy': 'Encrypt',
+ 'memcache_security_strategy': 'Encrypt'
}
- self.assertRaises(Exception, auth_token.AuthProtocol,
- FakeApp(), conf)
+ self.assertRaises(Exception, self.set_middleware, conf)
# test invalue memcache_security_strategy
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
- 'memcache_security_strategy': 'whatever',
+ 'memcache_security_strategy': 'whatever'
}
- self.assertRaises(Exception, auth_token.AuthProtocol,
- FakeApp(), conf)
+ self.assertRaises(Exception, self.set_middleware, conf)
# test missing memcache_secret_key
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
- 'memcache_security_strategy': 'mac',
+ 'memcache_security_strategy': 'mac'
}
- self.assertRaises(Exception, auth_token.AuthProtocol,
- FakeApp(), conf)
+ self.assertRaises(Exception, self.set_middleware, conf)
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
'memcache_security_strategy': 'Encrypt',
'memcache_secret_key': ''
}
- self.assertRaises(Exception, auth_token.AuthProtocol,
- FakeApp(), conf)
+ self.assertRaises(Exception, self.set_middleware, conf)
conf = {
- 'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211',
'memcache_security_strategy': 'mAc',
'memcache_secret_key': ''
}
- self.assertRaises(Exception, auth_token.AuthProtocol,
- FakeApp(), conf)
+ self.assertRaises(Exception, self.set_middleware, conf)
+
+
+class v2AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
+ """ v2 token specific tests.
+
+ There are some differences between how the auth-token middleware handles
+ v2 and v3 tokens over and above the token formats, namely:
+
+ - A v3 keystone server will auto scope a token to a user's default project
+ if no scope is specified. A v2 server assumes that the auth-token
+ middleware will do that.
+ - A v2 keystone server may issue a token without a catalog, even with a
+ tenant
+
+ The tests below were originally part of the generic AuthTokenMiddlewareTest
+ class, but now, since they really are v2 specifc, they are included here.
+
+ """
+ def assert_unscoped_default_tenant_auto_scopes(self, token):
+ """Unscoped v2 requests with a default tenant should "auto-scope."
+
+ The implied scope is the user's tenant ID.
+
+ """
+ req = webob.Request.blank('/')
+ req.headers['X-Auth-Token'] = token
+ body = self.middleware(req.environ, self.start_fake_response)
+ self.assertEqual(self.response_status, 200)
+ self.assertEqual(body, ['SUCCESS'])
+ self.assertTrue('keystone.token_info' in req.environ)
+
+ def test_default_tenant_uuid_token(self):
+ self.assert_unscoped_default_tenant_auto_scopes(UUID_TOKEN_DEFAULT)
+
+ def test_default_tenant_signed_token(self):
+ self.assert_unscoped_default_tenant_auto_scopes(SIGNED_TOKEN_SCOPED)
+
+ def assert_unscoped_token_receives_401(self, token):
+ """Unscoped requests with no default tenant ID should be rejected."""
+ req = webob.Request.blank('/')
+ req.headers['X-Auth-Token'] = token
+ self.middleware(req.environ, self.start_fake_response)
+ self.assertEqual(self.response_status, 401)
+ self.assertEqual(self.response_headers['WWW-Authenticate'],
+ "Keystone uri='https://keystone.example.com:1234'")
+
+ def test_unscoped_uuid_token_receives_401(self):
+ self.assert_unscoped_token_receives_401(UUID_TOKEN_UNSCOPED)
+
+ def test_unscoped_pki_token_receives_401(self):
+ self.assert_unscoped_token_receives_401(SIGNED_TOKEN_UNSCOPED)
+
+ def test_request_prevent_service_catalog_injection(self):
+ req = webob.Request.blank('/')
+ req.headers['X-Service-Catalog'] = '[]'
+ req.headers['X-Auth-Token'] = UUID_TOKEN_NO_SERVICE_CATALOG
+ body = self.middleware(req.environ, self.start_fake_response)
+ self.assertEqual(self.response_status, 200)
+ self.assertFalse(req.headers.get('X-Service-Catalog'))
+ self.assertEqual(body, ['SUCCESS'])
+
+ def test_valid_uuid_request_forced_to_2_0(self):
+ """ Test forcing auth_token to use lower api version.
+
+ By installing the v3 http hander, auth_token will be get
+ a version list that looks like a v3 server - from which it
+ would normally chose v3.0 as the auth version. However, here
+ we specify v2.0 in the configuration - which should force
+ auth_token to use that version instead.
+
+ """
+ conf = {
+ 'auth_host': 'keystone.example.com',
+ 'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
+ 'signing_dir': CERTDIR,
+ 'auth_version': 'v2.0'
+ }
+ self.set_middleware(fake_http=v3FakeHTTPConnection, conf=conf)
+ # This tests will only work is auth_token has chosen to use the
+ # lower, v2, api version
+ req = webob.Request.blank('/')
+ req.headers['X-Auth-Token'] = UUID_TOKEN_DEFAULT
+ body = self.middleware(req.environ, self.start_fake_response)
+ self.assertEqual(self.response_status, 200)
+ self.assertEqual("/testadmin/v2.0/tokens/%s" % UUID_TOKEN_DEFAULT,
+ v3FakeHTTPConnection.last_requested_url)
+
+ def test_invalid_auth_version_request(self):
+ conf = {
+ 'auth_host': 'keystone.example.com',
+ 'auth_port': 1234,
+ 'auth_admin_prefix': '/testadmin',
+ 'signing_dir': CERTDIR,
+ 'auth_version': 'v1.0' # v1.0 is no longer supported
+ }
+ self.assertRaises(Exception, self.set_middleware, conf)
+
+
+class v3AuthTokenMiddlewareTest(AuthTokenMiddlewareTest):
+ """ Test auth_token middleware with v3 tokens.
+
+ Re-execute the AuthTokenMiddlewareTest class tests, but with the
+ the auth_token middleware configured to expect v3 tokens back from
+ a keystone server.
+
+ This is done by configuring the AuthTokenMiddlewareTest class via
+ its Setup(), passing in v3 style data that will then be used by
+ the tests themselves. This approach has been used to ensure we
+ really are running the same tests for both v2 and v3 tokens.
+
+ There a few additional specific test for v3 only:
+
+ - We allow an unscoped token to be validated (as unscoped), where
+ as for v2 tokens, the auth_token middleware is expected to try and
+ auto-scope it (and fail if there is no default tenant)
+ - Domain scoped tokens
+
+ Since we don't specify an auth version for auth_token to use, by
+ definition we are thefore implicitely testing that it will use
+ the highest available auth version, i.e. v3.0
+
+ """
+ def setUp(self):
+ token_dict = {
+ 'uuid_token_default': v3_UUID_TOKEN_DEFAULT,
+ 'uuid_token_unscoped': v3_UUID_TOKEN_UNSCOPED,
+ 'signed_token_scoped': SIGNED_v3_TOKEN_SCOPED,
+ 'revoked_token': REVOKED_v3_TOKEN,
+ 'revoked_token_hash': REVOKED_v3_TOKEN_HASH
+ }
+ super(v3AuthTokenMiddlewareTest, self).setUp(
+ fake_app=v3FakeApp,
+ fake_http=v3FakeHTTPConnection,
+ token_dict=token_dict,
+ fake_memcache=v3FakeMemcache,
+ fake_memcache_ring=v3FakeSwiftMemcacheRing)
+
+ def assert_valid_last_url(self, token_id):
+ # Token ID is not part of the url in v3, so override
+ # this assert test in the base class
+ self.assertEqual('/testadmin/v3/auth/tokens',
+ v3FakeHTTPConnection.last_requested_url)
+
+ def test_valid_unscoped_uuid_request(self):
+ # Remove items that won't be in an unscoped token
+ delta_expected_env = {
+ 'HTTP_X_PROJECT_ID': None,
+ 'HTTP_X_PROJECT_NAME': None,
+ 'HTTP_X_PROJECT_DOMAIN_ID': None,
+ 'HTTP_X_PROJECT_DOMAIN_NAME': None,
+ 'HTTP_X_TENANT_ID': None,
+ 'HTTP_X_TENANT_NAME': None,
+ 'HTTP_X_ROLES': '',
+ 'HTTP_X_TENANT': None,
+ 'HTTP_X_ROLE': '',
+ }
+ self.set_middleware(expected_env=delta_expected_env)
+ self.assert_valid_request_200(v3_UUID_TOKEN_UNSCOPED,
+ with_catalog=False)
+ self.assertEqual('/testadmin/v3/auth/tokens',
+ v3FakeHTTPConnection.last_requested_url)
+
+ def test_domain_scoped_uuid_request(self):
+ # Modify items comapred to default token for a domain scope
+ delta_expected_env = {
+ 'HTTP_X_DOMAIN_ID': 'domain_id1',
+ 'HTTP_X_DOMAIN_NAME': 'domain_name1',
+ 'HTTP_X_PROJECT_ID': None,
+ 'HTTP_X_PROJECT_NAME': None,
+ 'HTTP_X_PROJECT_DOMAIN_ID': None,
+ 'HTTP_X_PROJECT_DOMAIN_NAME': None,
+ 'HTTP_X_TENANT_ID': None,
+ 'HTTP_X_TENANT_NAME': None,
+ 'HTTP_X_TENANT': None
+ }
+ self.set_middleware(expected_env=delta_expected_env)
+ self.assert_valid_request_200(v3_UUID_TOKEN_DOMAIN_SCOPED)
+ self.assertEqual('/testadmin/v3/auth/tokens',
+ v3FakeHTTPConnection.last_requested_url)
class TokenEncodingTest(testtools.TestCase):