@@ -38,7 +38,8 @@ cdef class CoreProtocol:
38
38
self .encoding = ' utf-8'
39
39
# type of `scram` is `SCRAMAuthentcation`
40
40
self .scram = None
41
- # type of `gss_ctx` is `gssapi.SecurityContext`
41
+ # type of `gss_ctx` is `gssapi.SecurityContext` or
42
+ # `sspilib.SecurityContext`
42
43
self .gss_ctx = None
43
44
44
45
self ._reset_result()
@@ -635,29 +636,33 @@ cdef class CoreProtocol:
635
636
)
636
637
self .scram = None
637
638
638
- elif status == AUTH_REQUIRED_GSS:
639
- self ._auth_gss_init()
640
- self .auth_msg = self ._auth_gss_step(None )
639
+ elif status in (AUTH_REQUIRED_GSS, AUTH_REQUIRED_SSPI):
640
+ # AUTH_REQUIRED_SSPI is the same as AUTH_REQUIRED_GSS, except that
641
+ # it uses protocol negotiation with SSPI clients. Both methods use
642
+ # AUTH_REQUIRED_GSS_CONTINUE for subsequent authentication steps.
643
+ if self .gss_ctx is not None :
644
+ self .result_type = RESULT_FAILED
645
+ self .result = apg_exc.InterfaceError(
646
+ ' duplicate GSSAPI/SSPI authentication request' )
647
+ else :
648
+ if self .con_params.gsslib == ' gssapi' :
649
+ self ._auth_gss_init_gssapi()
650
+ else :
651
+ self ._auth_gss_init_sspi(status == AUTH_REQUIRED_SSPI)
652
+ self .auth_msg = self ._auth_gss_step(None )
641
653
642
654
elif status == AUTH_REQUIRED_GSS_CONTINUE:
643
655
server_response = self .buffer.consume_message()
644
656
self .auth_msg = self ._auth_gss_step(server_response)
645
657
646
- elif status in (AUTH_REQUIRED_KERBEROS, AUTH_REQUIRED_SCMCRED,
647
- AUTH_REQUIRED_SSPI):
648
- self .result_type = RESULT_FAILED
649
- self .result = apg_exc.InterfaceError(
650
- ' unsupported authentication method requested by the '
651
- ' server: {!r}' .format(AUTH_METHOD_NAME[status]))
652
-
653
658
else :
654
659
self .result_type = RESULT_FAILED
655
660
self .result = apg_exc.InterfaceError(
656
661
' unsupported authentication method requested by the '
657
- ' server: {}' .format(status))
662
+ ' server: {!r }' .format(AUTH_METHOD_NAME.get( status, status) ))
658
663
659
- if status not in [ AUTH_SASL_CONTINUE, AUTH_SASL_FINAL,
660
- AUTH_REQUIRED_GSS_CONTINUE] :
664
+ if status not in ( AUTH_SASL_CONTINUE, AUTH_SASL_FINAL,
665
+ AUTH_REQUIRED_GSS_CONTINUE) :
661
666
self .buffer.discard_message()
662
667
663
668
cdef _auth_password_message_cleartext(self ):
@@ -714,25 +719,43 @@ cdef class CoreProtocol:
714
719
715
720
return msg
716
721
717
- cdef _auth_gss_init (self ):
722
+ cdef _auth_gss_init_gssapi (self ):
718
723
try :
719
724
import gssapi
720
725
except ModuleNotFoundError:
721
- raise RuntimeError (
722
- ' gssapi module not found; please install asyncpg[gssapi ] to '
723
- ' use asyncpg with Kerberos or GSSAPI authentication'
726
+ raise apg_exc.InterfaceError (
727
+ ' gssapi module not found; please install asyncpg[gssauth ] to '
728
+ ' use asyncpg with Kerberos/ GSSAPI/SSPI authentication'
724
729
) from None
725
730
731
+ self .gss_ctx = gssapi.SecurityContext(
732
+ name = gssapi.Name(self ._auth_gss_get_spn()), usage = ' initiate' )
733
+
734
+ cdef _auth_gss_init_sspi(self , bint negotiate):
735
+ try :
736
+ import sspilib
737
+ except ModuleNotFoundError:
738
+ raise apg_exc.InterfaceError(
739
+ ' sspilib module not found; please install asyncpg[gssauth] to '
740
+ ' use asyncpg with Kerberos/GSSAPI/SSPI authentication'
741
+ ) from None
742
+
743
+ self .gss_ctx = sspilib.ClientSecurityContext(
744
+ target_name = self ._auth_gss_get_spn(),
745
+ credential = sspilib.UserCredential(
746
+ protocol = ' Negotiate' if negotiate else ' Kerberos' ))
747
+
748
+ cdef _auth_gss_get_spn(self ):
726
749
service_name = self .con_params.krbsrvname or ' postgres'
727
750
# find the canonical name of the server host
728
751
if isinstance (self .address, str ):
729
- raise RuntimeError (' GSSAPI authentication is only supported for '
730
- ' TCP/IP connections' )
752
+ raise apg_exc.InternalClientError(
753
+ ' GSSAPI/SSPI authentication is only supported for TCP/IP '
754
+ ' connections' )
731
755
732
756
host = self .address[0 ]
733
757
host_cname = socket.gethostbyname_ex(host)[0 ]
734
- gss_name = gssapi.Name(f' {service_name}/{host_cname}' )
735
- self .gss_ctx = gssapi.SecurityContext(name = gss_name, usage = ' initiate' )
758
+ return f' {service_name}/{host_cname}'
736
759
737
760
cdef _auth_gss_step(self , bytes server_response):
738
761
cdef:
0 commit comments