mirror of
https://github.com/bringout/oca-server-auth.git
synced 2026-04-18 11:12:02 +02:00
Initial commit: OCA Server Auth packages (29 packages)
This commit is contained in:
commit
3ed80311c4
1325 changed files with 127292 additions and 0 deletions
|
|
@ -0,0 +1 @@
|
|||
from . import fake_idp, test_pysaml
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIID7TCCAtWgAwIBAgIUDBX/LJ1BPZOhb2vrDnwIasyEi+AwDQYJKoZIhvcNAQEL
|
||||
BQAwgYUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4wDAYDVQQH
|
||||
DAVQYXJpczEMMAoGA1UECgwDT0NBMQwwCgYDVQQLDANPQ0ExFDASBgNVBAMMC2V4
|
||||
YW1wbGUuY29tMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTIz
|
||||
MDEwMTExMDAyN1oXDTIzMDEzMTExMDAyN1owgYUxCzAJBgNVBAYTAkFVMRMwEQYD
|
||||
VQQIDApTb21lLVN0YXRlMQ4wDAYDVQQHDAVQYXJpczEMMAoGA1UECgwDT0NBMQww
|
||||
CgYDVQQLDANPQ0ExFDASBgNVBAMMC2V4YW1wbGUuY29tMR8wHQYJKoZIhvcNAQkB
|
||||
FhB0ZXN0QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
|
||||
AQEAvgeLRr1Q9aS/t8ZuC7/pZRHTB6sqamVwXyR7zh0v51yH7xBy9xs4zJWKneRn
|
||||
OJw46IogYhY+dyNWElbY+Ckcc6z1eJONiHNtOKAy07VtfhisGviRv1kLE56SHGgW
|
||||
fIXrOuFqj6F1yTfKyLtq2RZBzmbMTNG7z89rO2hqdTWqhyof9OGWtecrM7Ei9PnL
|
||||
tqULhQyh6n47KnIXfBMLIeQG7d/WyGU5CnO7yhHkS/51E9gI6g5G0VoueBVFErCl
|
||||
rjo0clMJrFVpanOG2USGgLfPkomSIv9ZL4SreFN27sbhTbkVWxbk7AOCFCQcaBIv
|
||||
RThpRrA9YRv2dB/X4yIi7UrrPwIDAQABo1MwUTAdBgNVHQ4EFgQU4WFoM/SL6qvT
|
||||
jV4YUwH3rggBqyIwHwYDVR0jBBgwFoAU4WFoM/SL6qvTjV4YUwH3rggBqyIwDwYD
|
||||
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYG+CTENdcmiKmyYg/n6H
|
||||
59RRgaLHSszjsKYdM5Uy/PmwfvPTRuxP38UhF0gWJTsLxWRjI0ejzdy5vzvxpoYl
|
||||
3pEyYxPzl+jfGA6AUSOfzTT/ksi7SZWak3cqJDFCdnfaCSjYyi+nKz5y6Bm1/fMy
|
||||
/3zJX92ELIWc8cTUItUIWjlITmxgLhIGr1wYaCinxkaEopGqkX85RYFaWKyYa5ok
|
||||
8MnoYbPrh/i9EekHUMBMPKWN3tWMMEROTtX9hmxSSTtgdQCahBaOCCU+m8PSNKEc
|
||||
UA8nSStaolv8t6aOyEb/Kzs7WSbd7v1ovZsy2FYmIRn0eHz8fpMAw2qk7mol6iao
|
||||
GQ==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAOOSWfQt/sCwMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMjEwMzAyMjE0ODMwWhcNMjEwNDAxMjE0ODMwWjBF
|
||||
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAtBIWiINhfAGdNHiNoG9xzz/PPQvgPKKz6RirHpcx7RAaCUcnsTcwZILH
|
||||
8aQ+lUbP62lIhCQ6o7/EuJt7AGxaOclM6E117o4eDQTcNPekhlqcUpFmL21V2dZg
|
||||
fL0tfwtu6ny6h/pJsgy66LF1YPgUrHA6e4TFWZOIXL5KHZivuveyeyu+hb14CwzB
|
||||
uvRMPgI7GeyxrhXlBsfntxKnD9cz4brnM2Eznuy8jSufvh3urCLphek9z4Bnfdsa
|
||||
7I7bCTAHjkUegWWXiAcRgd+Uluhcu1xQB1h4135dehVKnoaBTgFVUl8IDsqZN1yg
|
||||
/s7JzCA4iI23JEI0lHIJHTQ2GhpxtQIDAQABo1AwTjAdBgNVHQ4EFgQUXXIizCA8
|
||||
7eJpzk2fI2od4Inq4CQwHwYDVR0jBBgwFoAUXXIizCA87eJpzk2fI2od4Inq4CQw
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAIohOqEpKjkLBySQYTOdO
|
||||
ZkO31B3YnVXX9o2rN8ulVzfQNKcvKtGiME8ubcmrFg49sP+T3Y14hG4KfYFJ6zcC
|
||||
qveitFcmQ8NXHgByTqPGi9ZDdwEBKeHCHIym0R+24hEHw59iH42f6IVtfOZOR02P
|
||||
UyH1AKY2rktXNatOLysNYigf3JN6WlwahEaQ4XOrx+l0ez8H0mqR51rRUhdxuyBJ
|
||||
RPhqpWqrWhm6Hmcgv17/PwEQRHsFBF/qWb6iT9Cv0IR8v5gnlimaYyF1gacHKbkb
|
||||
gpit88248SSh1joHnhyoM+yTVnZiL5xAcuf5HicOmPOMLaKv6pJ/pqNz9jy0V3/U
|
||||
iw==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0EhaIg2F8AZ00
|
||||
eI2gb3HPP889C+A8orPpGKselzHtEBoJRyexNzBkgsfxpD6VRs/raUiEJDqjv8S4
|
||||
m3sAbFo5yUzoTXXujh4NBNw096SGWpxSkWYvbVXZ1mB8vS1/C27qfLqH+kmyDLro
|
||||
sXVg+BSscDp7hMVZk4hcvkodmK+697J7K76FvXgLDMG69Ew+AjsZ7LGuFeUGx+e3
|
||||
EqcP1zPhuuczYTOe7LyNK5++He6sIumF6T3PgGd92xrsjtsJMAeORR6BZZeIBxGB
|
||||
35SW6Fy7XFAHWHjXfl16FUqehoFOAVVSXwgOypk3XKD+zsnMIDiIjbckQjSUcgkd
|
||||
NDYaGnG1AgMBAAECggEAFpPCAYG/gkXNiRuoXjo64cpVWIkZp2CbABnYsrAwUVHY
|
||||
gdtLDbwmtCN1oEWAl0TWouSDdBX6yDcuGhtcc7QiJ+amXuX/aFanS+iVF4sJNNM9
|
||||
kFisoDusLPDlDh7GCozLblkPJidqgAl6kdxWJD9WkDxOCNifydhmm4I8VrOjLOTV
|
||||
w5gF0kjSRJmkNbPXiSOmaZzJEuxJYR9tqpanf2Fh/6Xf50Y5Hd+25mqbKUg21lZy
|
||||
AmHk5DYFZMugPFZgPzcZayr0LotlmfiExrMtrKoI2UX9J//MGrVil1bp8iVTJElJ
|
||||
fDPAivpcTjO5AlHRl2dfLM4ZUub6E4bw+DDGUX04DQKBgQDYRd0jp8S44763+6sA
|
||||
/F8jKNzxn5nCOkFUxMalh9+wzd433MsXj50QTVy3BGbm+mmo8ybqcQ2PVDI9mo3n
|
||||
73g4eG/BeSeYSDfk1gqIP5pb9gaAYKmKrNFphCqvrbJsCwzQ+bgrd3WKAv/isIlT
|
||||
756B/cl+RP6doDcTsFcBV9VjHwKBgQDVJdRnBAXgeeY8XP2sbd8Mr8Ev8Wf4IpTh
|
||||
dNN3A+ekcYyBkSdXeB8j+ToAcBWM5T69gofG61V6RHvAUPTf3AU9hWs1K2Xm5+Ln
|
||||
SEstDwDQ+DdcASCkgC48j2anvlla17Tf5Tcl6DaksxGHYNhT1U+hCiaDDU03mrDF
|
||||
aVIFbSNEqwKBgDZ0GMLiefindxyx5BOCd53Nqxu3OKqbqlliljWVaXAF1Z6xG/2Z
|
||||
rk0tfVujYxljEXl1h2XeAzEEXQX/xR0RwW5OfKz1CVAhVtlqPwqhIQdogaiPLgD5
|
||||
lFyB55GGJXdorNhtF77x/Ak8yhrUoi8dFQbb1IDTdFxRu6xcaPuwlsy3AoGBAKdG
|
||||
5hfmz1npMOiErkzpeVhygnHGyiqxsRfzYJYRyXSD7Jouuapqyj2oNX3seO03aHLA
|
||||
AyD4xf+LyXcX0eXxvWcX0xhKM9HwgGG0mdMF6EUX2BJrjButwRukCxNwTp39laT1
|
||||
Nb+ZK3E8W3Bcb8nzKWggGDNXeBdAXqS/UDCUA067AoGAUhJg9//JkziNYImCjInX
|
||||
nsYOH1ojfxtm5pr9+yiobKGdtUe36MQZmW6JMfiqJVjw5NYBgykbDXwrOkK97IeR
|
||||
2xtx0jcNLyadhzoWIHU/OvJRC1tsdOV30PdsiIRYbpuZjoBiDd2wODeylm9WG9Xz
|
||||
N6TBKKvJflx4Sw2o7+4EfZU=
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+B4tGvVD1pL+3
|
||||
xm4Lv+llEdMHqypqZXBfJHvOHS/nXIfvEHL3GzjMlYqd5Gc4nDjoiiBiFj53I1YS
|
||||
Vtj4KRxzrPV4k42Ic204oDLTtW1+GKwa+JG/WQsTnpIcaBZ8hes64WqPoXXJN8rI
|
||||
u2rZFkHOZsxM0bvPz2s7aGp1NaqHKh/04Za15yszsSL0+cu2pQuFDKHqfjsqchd8
|
||||
Ewsh5Abt39bIZTkKc7vKEeRL/nUT2AjqDkbRWi54FUUSsKWuOjRyUwmsVWlqc4bZ
|
||||
RIaAt8+SiZIi/1kvhKt4U3buxuFNuRVbFuTsA4IUJBxoEi9FOGlGsD1hG/Z0H9fj
|
||||
IiLtSus/AgMBAAECggEBAKuXUFJeHL7TNzMRAMmnT28uOypPiwtr8Z5X6Vtiy6DU
|
||||
0wIyDj3H3PAPkI2mcvaRSmngYAFyKJGX3N7OgTkElmZ1pWptgn3WDKf3MC4vQ2F7
|
||||
kd0A20q3cuMSaskvzC5BFvmiFoD/wMYjlP7RDVhdWqqv9IbhVAAAQcnxLUANZ6CH
|
||||
/xrieGuYavs62pSu5fnke7zRozdD1Mb7/oolAnycaLuoi1eZBh8wW8EJyFSxcZ5A
|
||||
pYF5kNqbwAdOZ22Tygxwu7lnh8PUOKxf9pTmO6uUYAJcn/Z3ZHtnBYsjU/LkfNPV
|
||||
hYLu1bKftm6UEZYwCXE3/ygop1q648NvCvtJB+Gbj9ECgYEA8nB+hS+7MLgi/dv8
|
||||
FCMJ9HBN76/nlwjOCTZIyIhCs5Jc6zJQGiDNLUFM/1mpBKUWWAss3g0dmJq32ish
|
||||
apsCUxabzWuKi44fDMEterJrGDWquyJK+jNPqfqOORLdMf0edNfZbjUxev7D52Ak
|
||||
4Ej3Ggy/fENd8QWLK6PZHV5X1MUCgYEAyKiWlawh7l8eBrba8UFQ4n1HiK/2uEud
|
||||
yQOLceSRmW/xC6ZCiR0ILinrtZWRxqQg+ZSS24hjnHhcdnRw8TRXx22TkTwGfAXW
|
||||
wKesPrtGJrn0ADuZwPkGewyeHPsisXNSiuGLPcLiOCoNNYgbIWJ2RknM1Xw+2p8C
|
||||
qYU8Si6l6DMCgYEA20v4ld7sExCszjZ72XcsXQhs5v+Vm9/iByEsSwA+XZJqLHFx
|
||||
VYEQNvxXeq8OnN37zR4msqDogY6J+XWEH5shSiksO28ofj3LRk1DJzZWeyqoSeem
|
||||
LJXXXKkAlw3COaJ9NzG8Qt0o6dmjORqVoK8/nTekyfFh+0+JaKsoDFG3XwUCgYBN
|
||||
tq2Ljj0d+wzAAPXO1kMjVO3tjGj7e53CinLpS2LwkCBFKMFAJVRTvLyjeSgaTNrQ
|
||||
jrBKAgrCQQNehT5wzJrqjA/JAfxo8EH6H3ZgXVuQCBjuNicYS9ossfhStRj8rPNd
|
||||
AnlRFDdVFUREZVBMn7u7AT4puJMHTOpVCVsOR/7NbQKBgApyR1WfsxZYi8vzVosQ
|
||||
jnMIW18lnZN3s6auyEvmpVowx0U0yd9QU3HHX1j8Kfq7D9uERCwBtIfn9fzZrZnu
|
||||
Xgbi9LMUT1z1jFXxJNgzaNmm0JHW9cD24BWNeQ60uxaRiGGmCyfmgqrGOXSn2R8w
|
||||
KoWEnnunZ9nehcD9dkWcH5zG
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC0EhaIg2F8AZ00
|
||||
eI2gb3HPP889C+A8orPpGKselzHtEBoJRyexNzBkgsfxpD6VRs/raUiEJDqjv8S4
|
||||
m3sAbFo5yUzoTXXujh4NBNw096SGWpxSkWYvbVXZ1mB8vS1/C27qfLqH+kmyDLro
|
||||
sXVg+BSscDp7hMVZk4hcvkodmK+697J7K76FvXgLDMG69Ew+AjsZ7LGuFeUGx+e3
|
||||
EqcP1zPhuuczYTOe7LyNK5++He6sIumF6T3PgGd92xrsjtsJMAeORR6BZZeIBxGB
|
||||
35SW6Fy7XFAHWHjXfl16FUqehoFOAVVSXwgOypk3XKD+zsnMIDiIjbckQjSUcgkd
|
||||
NDYaGnG1AgMBAAECggEAFpPCAYG/gkXNiRuoXjo64cpVWIkZp2CbABnYsrAwUVHY
|
||||
gdtLDbwmtCN1oEWAl0TWouSDdBX6yDcuGhtcc7QiJ+amXuX/aFanS+iVF4sJNNM9
|
||||
kFisoDusLPDlDh7GCozLblkPJidqgAl6kdxWJD9WkDxOCNifydhmm4I8VrOjLOTV
|
||||
w5gF0kjSRJmkNbPXiSOmaZzJEuxJYR9tqpanf2Fh/6Xf50Y5Hd+25mqbKUg21lZy
|
||||
AmHk5DYFZMugPFZgPzcZayr0LotlmfiExrMtrKoI2UX9J//MGrVil1bp8iVTJElJ
|
||||
fDPAivpcTjO5AlHRl2dfLM4ZUub6E4bw+DDGUX04DQKBgQDYRd0jp8S44763+6sA
|
||||
/F8jKNzxn5nCOkFUxMalh9+wzd433MsXj50QTVy3BGbm+mmo8ybqcQ2PVDI9mo3n
|
||||
73g4eG/BeSeYSDfk1gqIP5pb9gaAYKmKrNFphCqvrbJsCwzQ+bgrd3WKAv/isIlT
|
||||
756B/cl+RP6doDcTsFcBV9VjHwKBgQDVJdRnBAXgeeY8XP2sbd8Mr8Ev8Wf4IpTh
|
||||
dNN3A+ekcYyBkSdXeB8j+ToAcBWM5T69gofG61V6RHvAUPTf3AU9hWs1K2Xm5+Ln
|
||||
SEstDwDQ+DdcASCkgC48j2anvlla17Tf5Tcl6DaksxGHYNhT1U+hCiaDDU03mrDF
|
||||
aVIFbSNEqwKBgDZ0GMLiefindxyx5BOCd53Nqxu3OKqbqlliljWVaXAF1Z6xG/2Z
|
||||
rk0tfVujYxljEXl1h2XeAzEEXQX/xR0RwW5OfKz1CVAhVtlqPwqhIQdogaiPLgD5
|
||||
lFyB55GGJXdorNhtF77x/Ak8yhrUoi8dFQbb1IDTdFxRu6xcaPuwlsy3AoGBAKdG
|
||||
5hfmz1npMOiErkzpeVhygnHGyiqxsRfzYJYRyXSD7Jouuapqyj2oNX3seO03aHLA
|
||||
AyD4xf+LyXcX0eXxvWcX0xhKM9HwgGG0mdMF6EUX2BJrjButwRukCxNwTp39laT1
|
||||
Nb+ZK3E8W3Bcb8nzKWggGDNXeBdAXqS/UDCUA067AoGAUhJg9//JkziNYImCjInX
|
||||
nsYOH1ojfxtm5pr9+yiobKGdtUe36MQZmW6JMfiqJVjw5NYBgykbDXwrOkK97IeR
|
||||
2xtx0jcNLyadhzoWIHU/OvJRC1tsdOV30PdsiIRYbpuZjoBiDd2wODeylm9WG9Xz
|
||||
N6TBKKvJflx4Sw2o7+4EfZU=
|
||||
-----END PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJAOOSWfQt/sCwMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMjEwMzAyMjE0ODMwWhcNMjEwNDAxMjE0ODMwWjBF
|
||||
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAtBIWiINhfAGdNHiNoG9xzz/PPQvgPKKz6RirHpcx7RAaCUcnsTcwZILH
|
||||
8aQ+lUbP62lIhCQ6o7/EuJt7AGxaOclM6E117o4eDQTcNPekhlqcUpFmL21V2dZg
|
||||
fL0tfwtu6ny6h/pJsgy66LF1YPgUrHA6e4TFWZOIXL5KHZivuveyeyu+hb14CwzB
|
||||
uvRMPgI7GeyxrhXlBsfntxKnD9cz4brnM2Eznuy8jSufvh3urCLphek9z4Bnfdsa
|
||||
7I7bCTAHjkUegWWXiAcRgd+Uluhcu1xQB1h4135dehVKnoaBTgFVUl8IDsqZN1yg
|
||||
/s7JzCA4iI23JEI0lHIJHTQ2GhpxtQIDAQABo1AwTjAdBgNVHQ4EFgQUXXIizCA8
|
||||
7eJpzk2fI2od4Inq4CQwHwYDVR0jBBgwFoAUXXIizCA87eJpzk2fI2od4Inq4CQw
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAIohOqEpKjkLBySQYTOdO
|
||||
ZkO31B3YnVXX9o2rN8ulVzfQNKcvKtGiME8ubcmrFg49sP+T3Y14hG4KfYFJ6zcC
|
||||
qveitFcmQ8NXHgByTqPGi9ZDdwEBKeHCHIym0R+24hEHw59iH42f6IVtfOZOR02P
|
||||
UyH1AKY2rktXNatOLysNYigf3JN6WlwahEaQ4XOrx+l0ez8H0mqR51rRUhdxuyBJ
|
||||
RPhqpWqrWhm6Hmcgv17/PwEQRHsFBF/qWb6iT9Cv0IR8v5gnlimaYyF1gacHKbkb
|
||||
gpit88248SSh1joHnhyoM+yTVnZiL5xAcuf5HicOmPOMLaKv6pJ/pqNz9jy0V3/U
|
||||
iw==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
import os
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
|
||||
from saml2 import BINDING_HTTP_POST, BINDING_HTTP_REDIRECT, pack
|
||||
from saml2.authn_context import INTERNETPROTOCOLPASSWORD
|
||||
from saml2.config import Config as Saml2Config
|
||||
from saml2.metadata import create_metadata_string
|
||||
from saml2.saml import NAME_FORMAT_URI, NAMEID_FORMAT_PERSISTENT
|
||||
from saml2.server import Server
|
||||
|
||||
TYP = {"GET": [BINDING_HTTP_REDIRECT], "POST": [BINDING_HTTP_POST]}
|
||||
|
||||
|
||||
AUTHN = {
|
||||
"class_ref": INTERNETPROTOCOLPASSWORD,
|
||||
"authn_auth": "http://www.example.com/login",
|
||||
}
|
||||
|
||||
|
||||
BASE = "http://localhost:8000"
|
||||
CONFIG = {
|
||||
"entityid": "urn:mace:example.com:saml:example:idp",
|
||||
"name": "Rolands IdP",
|
||||
"service": {
|
||||
"aa": {
|
||||
"endpoints": {
|
||||
"attribute_service": [
|
||||
("%s/aap" % BASE, BINDING_HTTP_POST),
|
||||
]
|
||||
},
|
||||
},
|
||||
"aq": {
|
||||
"endpoints": {
|
||||
"authn_query_service": [("%s/aqs" % BASE, BINDING_HTTP_POST)]
|
||||
},
|
||||
},
|
||||
"idp": {
|
||||
"endpoints": {
|
||||
"single_sign_on_service": [
|
||||
("%s/sso/redirect" % BASE, BINDING_HTTP_REDIRECT),
|
||||
("%s/sso/post" % BASE, BINDING_HTTP_POST),
|
||||
],
|
||||
},
|
||||
"policy": {
|
||||
"default": {
|
||||
"lifetime": {"minutes": 15},
|
||||
"attribute_restrictions": None,
|
||||
"name_form": NAME_FORMAT_URI,
|
||||
},
|
||||
"urn:mace:example.com:saml:example:sp": {
|
||||
"lifetime": {"minutes": 5},
|
||||
"nameid_format": NAMEID_FORMAT_PERSISTENT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"debug": 1,
|
||||
"key_file": os.path.join(os.path.dirname(__file__), "data", "idp.pem"),
|
||||
"cert_file": os.path.join(os.path.dirname(__file__), "data", "idp.pem"),
|
||||
"organization": {
|
||||
"name": "Example",
|
||||
"display_name": [("Example", "uk")],
|
||||
"url": "http://www.example.com/",
|
||||
},
|
||||
"contact_person": [
|
||||
{
|
||||
"given_name": "Admin",
|
||||
"sur_name": "Admin",
|
||||
"email_address": ["admin@example.com"],
|
||||
"contact_type": "technical",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class DummyResponse:
|
||||
def __init__(self, status, data, headers=None):
|
||||
self.status_code = status
|
||||
self.text = data
|
||||
self.headers = headers or []
|
||||
self.content = data
|
||||
|
||||
def _unpack(self, ver="SAMLResponse"):
|
||||
"""
|
||||
Unpack the response form
|
||||
"""
|
||||
_str = self.text
|
||||
|
||||
sr_str = 'name="%s" value="' % ver
|
||||
rs_str = 'name="RelayState" value="'
|
||||
|
||||
i = _str.find(sr_str)
|
||||
i += len(sr_str)
|
||||
j = _str.find('"', i)
|
||||
|
||||
sr = _str[i:j]
|
||||
|
||||
start = _str.find(rs_str, j)
|
||||
start += len(rs_str)
|
||||
end = _str.find('"', start)
|
||||
|
||||
rs = _str[start:end]
|
||||
|
||||
return {ver: sr, "RelayState": rs}
|
||||
|
||||
|
||||
class FakeIDP(Server):
|
||||
def __init__(self, metadatas=None, settings=None):
|
||||
if settings is None:
|
||||
settings = CONFIG
|
||||
if metadatas:
|
||||
settings.update({"metadata": {"inline": metadatas}})
|
||||
|
||||
config = Saml2Config()
|
||||
config.load(settings)
|
||||
config.allow_unknown_attributes = True
|
||||
Server.__init__(self, config=config)
|
||||
|
||||
def get_metadata(self):
|
||||
return create_metadata_string(
|
||||
None,
|
||||
config=self.config,
|
||||
sign=True,
|
||||
valid=True,
|
||||
cert=CONFIG.get("cert_file"),
|
||||
keyfile=CONFIG.get("key_file"),
|
||||
)
|
||||
|
||||
def fake_login(self, url):
|
||||
# Assumes GET query and HTTP_REDIRECT only.
|
||||
# This is all that auth_pysaml currently supports.
|
||||
parsed_url = urlparse(url)
|
||||
qs_dict = parse_qs(parsed_url.query)
|
||||
|
||||
samlreq = qs_dict["SAMLRequest"][0]
|
||||
rstate = qs_dict["RelayState"][0]
|
||||
|
||||
# process the logon request, and automatically "login"
|
||||
return self.authn_request_endpoint(samlreq, BINDING_HTTP_REDIRECT, rstate)
|
||||
|
||||
def authn_request_endpoint(self, req, binding, relay_state):
|
||||
req = self.parse_authn_request(req, binding)
|
||||
if req.message.protocol_binding == BINDING_HTTP_REDIRECT:
|
||||
_binding = BINDING_HTTP_POST
|
||||
else:
|
||||
_binding = req.message.protocol_binding
|
||||
|
||||
resp_args = self.response_args(req.message, [_binding])
|
||||
|
||||
identity = {
|
||||
"surName": "Example",
|
||||
"givenName": "Test",
|
||||
"title": "Ind",
|
||||
"mail": "test@example.com",
|
||||
}
|
||||
|
||||
resp_args.update({"sign_assertion": True, "sign_response": True})
|
||||
|
||||
authn_resp = self.create_authn_response(
|
||||
identity, userid=identity.get("mail"), authn=AUTHN, **resp_args
|
||||
)
|
||||
|
||||
_dict = pack.factory(
|
||||
_binding, authn_resp, resp_args["destination"], relay_state, "SAMLResponse"
|
||||
)
|
||||
|
||||
return DummyResponse(**_dict)
|
||||
|
||||
|
||||
class UnsignedFakeIDP(FakeIDP):
|
||||
def create_authn_response(
|
||||
self,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs["sign_assertion"] = False
|
||||
return super().create_authn_response(*args, **kwargs)
|
||||
|
|
@ -0,0 +1,488 @@
|
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
import base64
|
||||
import html
|
||||
import os
|
||||
import os.path as osp
|
||||
from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
|
||||
import responses
|
||||
from saml2.sigver import SignatureError
|
||||
|
||||
from odoo.exceptions import AccessDenied, UserError, ValidationError
|
||||
from odoo.tests import HttpCase, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
from .fake_idp import CONFIG, FakeIDP, UnsignedFakeIDP
|
||||
|
||||
|
||||
@tagged("saml", "post_install", "-at_install")
|
||||
class TestPySaml(HttpCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
sp_pem_public = None
|
||||
sp_pem_private = None
|
||||
|
||||
with open(
|
||||
os.path.join(os.path.dirname(__file__), "data", "sp.pem"),
|
||||
"r",
|
||||
encoding="UTF-8",
|
||||
) as file:
|
||||
sp_pem_public = file.read()
|
||||
|
||||
with open(
|
||||
os.path.join(os.path.dirname(__file__), "data", "sp.key"),
|
||||
"r",
|
||||
encoding="UTF-8",
|
||||
) as file:
|
||||
sp_pem_private = file.read()
|
||||
|
||||
self.saml_provider = self.env["auth.saml.provider"].create(
|
||||
{
|
||||
"name": "SAML Provider Demo",
|
||||
"idp_metadata": FakeIDP().get_metadata(),
|
||||
"sp_pem_public": base64.b64encode(sp_pem_public.encode()),
|
||||
"sp_pem_private": base64.b64encode(sp_pem_private.encode()),
|
||||
"body": "Login with Authentic",
|
||||
"active": True,
|
||||
"sig_alg": "SIG_RSA_SHA1",
|
||||
"matching_attribute": "mail",
|
||||
}
|
||||
)
|
||||
self.url_saml_request = (
|
||||
"/auth_saml/get_auth_request?pid=%d" % self.saml_provider.id
|
||||
)
|
||||
|
||||
self.idp = FakeIDP([self.saml_provider._metadata_string()])
|
||||
|
||||
# Create a user with only password, and another with both password and saml id
|
||||
self.user, self.user2 = (
|
||||
self.env["res.users"]
|
||||
.with_context(no_reset_password=True, tracking_disable=True)
|
||||
.create(
|
||||
[
|
||||
{
|
||||
"name": "User",
|
||||
"email": "test@example.com",
|
||||
"login": "test@example.com",
|
||||
"password": "Lu,ums-7vRU>0i]=YDLa",
|
||||
},
|
||||
{
|
||||
"name": "User with SAML",
|
||||
"email": "user@example.com",
|
||||
"login": "user@example.com",
|
||||
"password": "NesTNSte9340D720te>/-A",
|
||||
"saml_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"saml_provider_id": self.saml_provider.id,
|
||||
"saml_uid": "user@example.com",
|
||||
},
|
||||
)
|
||||
],
|
||||
},
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def test_ensure_provider_appears_on_login(self):
|
||||
# SAML provider should be listed in the login page
|
||||
response = self.url_open("/web/login")
|
||||
self.assertIn("Login with Authentic", response.text)
|
||||
self.assertIn(self.url_saml_request, response.text)
|
||||
|
||||
def test_ensure_provider_appears_on_login_with_redirect_param(self):
|
||||
"""Test that SAML provider is listed in the login page keeping the redirect"""
|
||||
response = self.url_open(
|
||||
"/web/login?redirect=%2Fweb%23action%3D37%26model%3Dir.module.module%26view"
|
||||
"_type%3Dkanban%26menu_id%3D5"
|
||||
)
|
||||
self.assertIn("Login with Authentic", response.text)
|
||||
self.assertIn(
|
||||
"/auth_saml/get_auth_request?pid={}&redirect=%2Fweb%23action%3D37%26mod"
|
||||
"el%3Dir.module.module%26view_type%3Dkanban%26menu_id%3D5".format(
|
||||
self.saml_provider.id
|
||||
),
|
||||
response.text,
|
||||
)
|
||||
|
||||
def test_ensure_metadata_present(self):
|
||||
response = self.url_open(
|
||||
"/auth_saml/metadata?p=%d&d=%s"
|
||||
% (self.saml_provider.id, self.env.cr.dbname)
|
||||
)
|
||||
|
||||
self.assertTrue(response.ok)
|
||||
self.assertTrue("xml" in response.headers.get("Content-Type"))
|
||||
|
||||
def test_ensure_get_auth_request_redirects(self):
|
||||
response = self.url_open(
|
||||
"/auth_saml/get_auth_request?pid=%d" % self.saml_provider.id,
|
||||
allow_redirects=False,
|
||||
)
|
||||
self.assertTrue(response.ok)
|
||||
self.assertEqual(response.status_code, 303)
|
||||
self.assertIn(
|
||||
"http://localhost:8000/sso/redirect?SAMLRequest=",
|
||||
response.headers.get("Location"),
|
||||
)
|
||||
|
||||
def test_login_no_saml(self):
|
||||
"""
|
||||
Login with a user account, but without any SAML provider setup
|
||||
against the user
|
||||
"""
|
||||
# Standard login using password
|
||||
self.authenticate(user="test@example.com", password="Lu,ums-7vRU>0i]=YDLa")
|
||||
self.assertEqual(self.session.uid, self.user.id)
|
||||
|
||||
self.logout()
|
||||
|
||||
# Try to log in with a non-existing SAML token
|
||||
with self.assertRaises(AccessDenied):
|
||||
self.authenticate(user="test@example.com", password="test_saml_token")
|
||||
|
||||
redirect_url = self.saml_provider._get_auth_request()
|
||||
self.assertIn("http://localhost:8000/sso/redirect?SAMLRequest=", redirect_url)
|
||||
|
||||
response = self.idp.fake_login(redirect_url)
|
||||
self.assertEqual(200, response.status_code)
|
||||
unpacked_response = response._unpack()
|
||||
|
||||
with self.assertRaises(AccessDenied):
|
||||
self.env["res.users"].sudo().auth_saml(
|
||||
self.saml_provider.id, unpacked_response.get("SAMLResponse"), None
|
||||
)
|
||||
|
||||
def add_provider_to_user(self):
|
||||
"""Add a provider to self.user"""
|
||||
self.user.write(
|
||||
{
|
||||
"saml_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"saml_provider_id": self.saml_provider.id,
|
||||
"saml_uid": "test@example.com",
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
def test_login_with_saml(self):
|
||||
self.add_provider_to_user()
|
||||
|
||||
redirect_url = self.saml_provider._get_auth_request()
|
||||
self.assertIn("http://localhost:8000/sso/redirect?SAMLRequest=", redirect_url)
|
||||
|
||||
response = self.idp.fake_login(redirect_url)
|
||||
self.assertEqual(200, response.status_code)
|
||||
unpacked_response = response._unpack()
|
||||
|
||||
(database, login, token) = (
|
||||
self.env["res.users"]
|
||||
.sudo()
|
||||
.auth_saml(
|
||||
self.saml_provider.id, unpacked_response.get("SAMLResponse"), None
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(database, self.env.cr.dbname)
|
||||
self.assertEqual(login, self.user.login)
|
||||
|
||||
# We should not be able to log in with the wrong token
|
||||
with self.assertRaises(AccessDenied):
|
||||
self.authenticate(
|
||||
user="test@example.com", password="{}-WRONG".format(token)
|
||||
)
|
||||
|
||||
# User should now be able to log in with the token
|
||||
self.authenticate(user="test@example.com", password=token)
|
||||
|
||||
def test_disallow_user_password_when_changing_ir_config_parameter(self):
|
||||
"""Test that disabling users from having both a password and SAML ids remove
|
||||
users password."""
|
||||
# change the option
|
||||
self.browse_ref(
|
||||
"auth_saml.allow_saml_uid_and_internal_password"
|
||||
).value = "False"
|
||||
# The password should be blank and the user should not be able to connect
|
||||
with self.assertRaises(AccessDenied):
|
||||
self.authenticate(
|
||||
user="user@example.com", password="NesTNSte9340D720te>/-A"
|
||||
)
|
||||
|
||||
def test_disallow_user_password_new_user(self):
|
||||
"""Test that a new user can not be set up with both password and SAML ids when
|
||||
the disallow option is set."""
|
||||
# change the option
|
||||
self.browse_ref(
|
||||
"auth_saml.allow_saml_uid_and_internal_password"
|
||||
).value = "False"
|
||||
with self.assertRaises(UserError):
|
||||
self.env["res.users"].with_context(no_reset_password=True).create(
|
||||
{
|
||||
"name": "New user with SAML",
|
||||
"email": "user2@example.com",
|
||||
"login": "user2@example.com",
|
||||
"password": "NesTNSte9340D720te>/-A",
|
||||
"saml_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"saml_provider_id": self.saml_provider.id,
|
||||
"saml_uid": "user2",
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
def test_disallow_user_password_no_password_set(self):
|
||||
"""Test that a new user with SAML ids can not have its password set up when the
|
||||
disallow option is set."""
|
||||
# change the option
|
||||
self.browse_ref(
|
||||
"auth_saml.allow_saml_uid_and_internal_password"
|
||||
).value = "False"
|
||||
# Create a new user with only SAML ids
|
||||
user = (
|
||||
self.env["res.users"]
|
||||
.with_context(no_reset_password=True, tracking_disable=True)
|
||||
.create(
|
||||
{
|
||||
"name": "New user with SAML",
|
||||
"email": "user2@example.com",
|
||||
"login": "user2@example.com",
|
||||
"saml_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"saml_provider_id": self.saml_provider.id,
|
||||
"saml_uid": "unused",
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
)
|
||||
# Assert that the user password can not be set
|
||||
with self.assertRaises(ValidationError):
|
||||
user.password = "new password"
|
||||
|
||||
def test_disallow_user_password(self):
|
||||
"""Test that existing user password is deleted when adding an SAML provider when
|
||||
the disallow option is set."""
|
||||
# change the option
|
||||
self.browse_ref(
|
||||
"auth_saml.allow_saml_uid_and_internal_password"
|
||||
).value = "False"
|
||||
# Test that existing user password is deleted when adding an SAML provider
|
||||
self.authenticate(user="test@example.com", password="Lu,ums-7vRU>0i]=YDLa")
|
||||
self.add_provider_to_user()
|
||||
with self.assertRaises(AccessDenied):
|
||||
self.authenticate(user="test@example.com", password="Lu,ums-7vRU>0i]=YDLa")
|
||||
|
||||
def test_disallow_user_admin_can_have_password(self):
|
||||
"""Test that admin can have its password set even if the disallow option is set."""
|
||||
# change the option
|
||||
self.browse_ref(
|
||||
"auth_saml.allow_saml_uid_and_internal_password"
|
||||
).value = "False"
|
||||
# Test base.user_admin exception
|
||||
self.env.ref("base.user_admin").password = "nNRST4j*->sEatNGg._!"
|
||||
|
||||
def test_db_filtering(self):
|
||||
# change filter to only allow our db.
|
||||
with patch("odoo.http.db_filter", new=lambda *args, **kwargs: []):
|
||||
self.add_provider_to_user()
|
||||
|
||||
redirect_url = self.saml_provider._get_auth_request()
|
||||
response = self.idp.fake_login(redirect_url)
|
||||
unpacked_response = response._unpack()
|
||||
|
||||
for key in unpacked_response:
|
||||
unpacked_response[key] = html.unescape(unpacked_response[key])
|
||||
response = self.url_open("/auth_saml/signin", data=unpacked_response)
|
||||
self.assertFalse(response.ok)
|
||||
self.assertIn(response.status_code, [400, 404])
|
||||
|
||||
def test_redirect_after_login(self):
|
||||
"""Test that providing a redirect will be kept after SAML login."""
|
||||
self.add_provider_to_user()
|
||||
|
||||
redirect_url = self.saml_provider._get_auth_request(
|
||||
{
|
||||
"r": "%2Fweb%23action%3D37%26model%3Dir.module.module%26view_type%3Dkan"
|
||||
"ban%26menu_id%3D5"
|
||||
}
|
||||
)
|
||||
response = self.idp.fake_login(redirect_url)
|
||||
unpacked_response = response._unpack()
|
||||
|
||||
for key in unpacked_response:
|
||||
unpacked_response[key] = html.unescape(unpacked_response[key])
|
||||
response = self.url_open(
|
||||
"/auth_saml/signin",
|
||||
data=unpacked_response,
|
||||
allow_redirects=True,
|
||||
timeout=300,
|
||||
)
|
||||
self.assertTrue(response.ok)
|
||||
self.assertEqual(
|
||||
response.url,
|
||||
self.base_url()
|
||||
+ "/web#action=37&model=ir.module.module&view_type=kanban&menu_id=5",
|
||||
)
|
||||
|
||||
def test_disallow_user_password_when_changing_settings(self):
|
||||
"""Test that disabling the setting will remove passwords from related users"""
|
||||
# We activate the settings to allow password login
|
||||
self.env["res.config.settings"].create(
|
||||
{
|
||||
"allow_saml_uid_and_internal_password": True,
|
||||
}
|
||||
).execute()
|
||||
|
||||
# Test the user can login with the password
|
||||
self.authenticate(user="user@example.com", password="NesTNSte9340D720te>/-A")
|
||||
|
||||
self.env["res.config.settings"].create(
|
||||
{
|
||||
"allow_saml_uid_and_internal_password": False,
|
||||
}
|
||||
).execute()
|
||||
|
||||
with self.assertRaises(AccessDenied):
|
||||
self.authenticate(
|
||||
user="user@example.com", password="NesTNSte9340D720te>/-A"
|
||||
)
|
||||
|
||||
@responses.activate
|
||||
def test_download_metadata(self):
|
||||
expected_metadata = self.idp.get_metadata()
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"http://localhost:8000/metadata",
|
||||
status=200,
|
||||
content_type="text/xml",
|
||||
body=expected_metadata,
|
||||
)
|
||||
self.saml_provider.idp_metadata_url = "http://localhost:8000/metadata"
|
||||
self.saml_provider.idp_metadata = ""
|
||||
self.saml_provider.action_refresh_metadata_from_url()
|
||||
self.assertEqual(self.saml_provider.idp_metadata, expected_metadata)
|
||||
|
||||
@responses.activate
|
||||
def test_download_metadata_no_provider(self):
|
||||
self.saml_provider.idp_metadata_url = "http://localhost:8000/metadata"
|
||||
self.saml_provider.idp_metadata = ""
|
||||
self.saml_provider.active = False
|
||||
self.saml_provider.action_refresh_metadata_from_url()
|
||||
self.assertFalse(self.saml_provider.idp_metadata)
|
||||
|
||||
@responses.activate
|
||||
def test_download_metadata_error(self):
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"http://localhost:8000/metadata",
|
||||
status=500,
|
||||
content_type="text/xml",
|
||||
)
|
||||
self.saml_provider.idp_metadata_url = "http://localhost:8000/metadata"
|
||||
self.saml_provider.idp_metadata = ""
|
||||
with self.assertRaises(UserError):
|
||||
self.saml_provider.action_refresh_metadata_from_url()
|
||||
self.assertFalse(self.saml_provider.idp_metadata)
|
||||
|
||||
@responses.activate
|
||||
def test_download_metadata_no_update(self):
|
||||
expected_metadata = self.idp.get_metadata()
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"http://localhost:8000/metadata",
|
||||
status=200,
|
||||
content_type="text/xml",
|
||||
body=expected_metadata,
|
||||
)
|
||||
self.saml_provider.idp_metadata_url = "http://localhost:8000/metadata"
|
||||
self.saml_provider.idp_metadata = expected_metadata
|
||||
self.saml_provider.action_refresh_metadata_from_url()
|
||||
self.assertEqual(self.saml_provider.idp_metadata, expected_metadata)
|
||||
|
||||
@responses.activate
|
||||
def test_login_with_saml_metadata_empty(self):
|
||||
self.saml_provider.idp_metadata_url = "http://localhost:8000/metadata"
|
||||
self.saml_provider.idp_metadata = ""
|
||||
expected_metadata = self.idp.get_metadata()
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"http://localhost:8000/metadata",
|
||||
status=200,
|
||||
content_type="text/xml",
|
||||
body=expected_metadata,
|
||||
)
|
||||
self.test_login_with_saml()
|
||||
self.assertEqual(self.saml_provider.idp_metadata, expected_metadata)
|
||||
|
||||
@responses.activate
|
||||
def test_login_with_saml_metadata_key_changed(self):
|
||||
settings = deepcopy(CONFIG)
|
||||
settings["key_file"] = osp.join(
|
||||
osp.dirname(__file__), "data", "key_idp_expired.pem"
|
||||
)
|
||||
settings["cert"] = osp.join(
|
||||
osp.dirname(__file__), "data", "key_idp_expired.pem"
|
||||
)
|
||||
expired_idp = FakeIDP(settings=settings)
|
||||
self.saml_provider.idp_metadata = expired_idp.get_metadata()
|
||||
self.saml_provider.idp_metadata_url = "http://localhost:8000/metadata"
|
||||
up_to_date_metadata = self.idp.get_metadata()
|
||||
self.assertNotEqual(self.saml_provider.idp_metadata, up_to_date_metadata)
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"http://localhost:8000/metadata",
|
||||
status=200,
|
||||
content_type="text/xml",
|
||||
body=up_to_date_metadata,
|
||||
)
|
||||
self.test_login_with_saml()
|
||||
|
||||
@responses.activate
|
||||
def test_login_with_saml_unsigned_response(self):
|
||||
self.add_provider_to_user()
|
||||
self.saml_provider.idp_metadata_url = "http://localhost:8000/metadata"
|
||||
unsigned_idp = UnsignedFakeIDP([self.saml_provider._metadata_string()])
|
||||
redirect_url = self.saml_provider._get_auth_request()
|
||||
self.assertIn("http://localhost:8000/sso/redirect?SAMLRequest=", redirect_url)
|
||||
|
||||
response = unsigned_idp.fake_login(redirect_url)
|
||||
self.assertEqual(200, response.status_code)
|
||||
unpacked_response = response._unpack()
|
||||
|
||||
responses.add(
|
||||
responses.GET,
|
||||
"http://localhost:8000/metadata",
|
||||
status=200,
|
||||
content_type="text/xml",
|
||||
body=self.saml_provider.idp_metadata,
|
||||
)
|
||||
with (
|
||||
self.assertRaises(SignatureError),
|
||||
mute_logger("saml2.entity"),
|
||||
mute_logger("saml2.client_base"),
|
||||
):
|
||||
(database, login, token) = (
|
||||
self.env["res.users"]
|
||||
.sudo()
|
||||
.auth_saml(
|
||||
self.saml_provider.id, unpacked_response.get("SAMLResponse"), None
|
||||
)
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue