2
2
__copyright__ = "Copyright The ORAS Authors."
3
3
__license__ = "Apache-2.0"
4
4
5
-
5
+ import json
6
+ import subprocess
6
7
from typing import Optional
7
8
8
9
import requests
@@ -23,7 +24,7 @@ class AuthBackend:
23
24
_tls_verify : bool
24
25
25
26
def __init__ (self , * args , ** kwargs ):
26
- self ._auths : dict = {}
27
+ self ._auth_config : dict = {}
27
28
self .prefix : str = "https"
28
29
29
30
def get_auth_header (self ):
@@ -48,20 +49,41 @@ def logout(self, hostname: str):
48
49
:type hostname: str
49
50
"""
50
51
self ._logout ()
51
- if not self ._auths :
52
+ if not self ._auth_config or not self . _auth_config . get ( "auths" ) :
52
53
logger .info (f"You are not logged in to { hostname } " )
53
54
return
54
55
55
56
for host in oras .utils .iter_localhosts (hostname ):
56
- if host in self ._auths :
57
- del self ._auths [host ]
57
+ auths = self ._auth_config .get ("auths" , {})
58
+ if host in auths :
59
+ del auths [host ]
58
60
logger .info (f"You have successfully logged out of { hostname } " )
59
61
return
60
62
logger .info (f"You are not logged in to { hostname } " )
61
63
62
64
def _logout (self ):
63
65
pass
64
66
67
+ def _get_auth_from_creds_store (self , binary : str , hostname : str ) -> str :
68
+ try :
69
+ proc = subprocess .run (
70
+ [binary , "get" ],
71
+ input = hostname .encode (),
72
+ stdout = subprocess .PIPE ,
73
+ stderr = subprocess .PIPE ,
74
+ check = True ,
75
+ )
76
+ except FileNotFoundError as exc :
77
+ raise RuntimeError (
78
+ f"Credential helper '{ binary } ' not found in PATH"
79
+ ) from exc
80
+ except subprocess .CalledProcessError as exc :
81
+ raise RuntimeError (
82
+ f"Credential helper '{ binary } ' failed: { exc .stderr .decode ().strip ()} "
83
+ ) from exc
84
+ payload = json .loads (proc .stdout )
85
+ return auth_utils .get_basic_auth (payload ["Username" ], payload ["Secret" ])
86
+
65
87
def _load_auth (self , hostname : str ) -> bool :
66
88
"""
67
89
Look for and load a named authentication token.
@@ -70,21 +92,35 @@ def _load_auth(self, hostname: str) -> bool:
70
92
:type hostname: str
71
93
"""
72
94
# Note that the hostname can be defined without a token
73
- if hostname in self ._auths :
74
- auth = self ._auths [hostname ].get ("auth" )
75
-
76
- # Case 1: they use a credsStore we don't know how to read
77
- if not auth and "credsStore" in self ._auths [hostname ]:
78
- logger .warning (
79
- '"credsStore" found in your ~/.docker/config.json, which is not supported by oras-py. Remove it, docker login, and try again.'
80
- )
81
- return False
95
+ auths = self ._auth_config .get ("auths" , {})
96
+ auth = auths .get (hostname )
97
+ if auth is not None :
98
+ auth = auths [hostname ].get ("auth" )
82
99
83
- # Case 2: no auth there (wonky file)
84
- elif not auth :
100
+ if not auth :
101
+ # no auth there (wonky file)
85
102
return False
86
103
self ._basic_auth = auth
87
104
return True
105
+ # Check for credsStore:
106
+ if self ._auth_config .get ("credsStore" ):
107
+ auth = self ._get_auth_from_creds_store (
108
+ self ._auth_config ["credsStore" ], hostname
109
+ )
110
+ if auth is not None :
111
+ self ._basic_auth = auth
112
+ auths [hostname ] = {"auth" : auth }
113
+ return True
114
+ # Check for credHelper
115
+ if self ._auth_config .get ("credHelpers" , {}).get (hostname ):
116
+ auth = self ._get_auth_from_creds_store (
117
+ self ._auth_config ["credHelpers" ][hostname ], hostname
118
+ )
119
+ if auth is not None :
120
+ self ._basic_auth = auth
121
+ auths [hostname ] = {"auth" : auth }
122
+ return True
123
+
88
124
return False
89
125
90
126
@decorator .ensure_container
@@ -100,8 +136,8 @@ def load_configs(self, container: container_type, configs: Optional[list] = None
100
136
:param configs: list of configs to read (optional)
101
137
:type configs: list
102
138
"""
103
- if not self ._auths :
104
- self ._auths = auth_utils .load_configs (configs )
139
+ if not self ._auth_config :
140
+ self ._auth_config = auth_utils .load_configs (configs )
105
141
for registry in oras .utils .iter_localhosts (container .registry ): # type: ignore
106
142
if self ._load_auth (registry ):
107
143
return
0 commit comments