summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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):