1212from pathlib import Path
1313from typing import Dict , Any , Optional , Tuple
1414
15+
1516class AuthenticationHandler :
1617 """
1718 Handles authentication with remote services.
1819 """
19-
20+
2021 def __init__ (self , verbose : int = 0 ):
2122 """
2223 Initialize the authentication handler.
23-
24+
2425 Args:
2526 verbose: Verbosity level (0=normal, 1=info, 2=debug)
2627 """
2728 self .verbose = verbose
2829 self .logger = logging .getLogger ("repokit.auth" )
29-
30+
3031 # Default locations for credentials
3132 self .user_home = Path .home ()
3233 self .default_creds_path = self .user_home / ".repokit" / "credentials.json"
33-
34+
3435 # Initialize with empty credentials
3536 self .credentials = {}
36-
37+
3738 def load_credentials (self , credentials_file : Optional [str ] = None ) -> bool :
3839 """
3940 Load credentials from a file.
40-
41+
4142 Args:
4243 credentials_file: Path to credentials file (or None to use default)
43-
44+
4445 Returns:
4546 True if loaded successfully, False otherwise
4647 """
@@ -52,48 +53,50 @@ def load_credentials(self, credentials_file: Optional[str] = None) -> bool:
5253 if self .verbose >= 1 :
5354 self .logger .info ("No credentials file found" )
5455 return False
55-
56+
5657 try :
57- with open (creds_path , 'r' ) as f :
58+ with open (creds_path , "r" ) as f :
5859 self .credentials = json .load (f )
59-
60+
6061 if self .verbose >= 1 :
6162 self .logger .info (f"Loaded credentials from { creds_path } " )
6263 return True
6364 except Exception as e :
6465 self .logger .error (f"Error loading credentials: { str (e )} " )
6566 return False
66-
67- def get_token (self , service : str , token_command : Optional [str ] = None ) -> Optional [str ]:
67+
68+ def get_token (
69+ self , service : str , token_command : Optional [str ] = None
70+ ) -> Optional [str ]:
6871 """
6972 Get authentication token for a service.
70-
73+
7174 Tries multiple sources in this order:
7275 1. Command provided token command
7376 2. Environment variable
7477 3. Credentials file
75-
78+
7679 Args:
7780 service: Service name ('github' or 'gitlab')
7881 token_command: Optional command to retrieve token from password manager
79-
82+
8083 Returns:
8184 Authentication token or None if not found
8285 """
8386 # Standardize service name
8487 service = service .lower ()
85-
88+
8689 # 1. Try token command if provided
8790 if token_command :
8891 try :
8992 if self .verbose >= 2 :
9093 self .logger .debug (f"Running token command: { token_command } " )
9194 result = subprocess .run (
92- token_command ,
93- shell = True ,
95+ token_command ,
96+ shell = True ,
9497 check = True ,
9598 capture_output = True ,
96- text = True
99+ text = True ,
97100 )
98101 token = result .stdout .strip ()
99102 if token :
@@ -102,63 +105,63 @@ def get_token(self, service: str, token_command: Optional[str] = None) -> Option
102105 return token
103106 except Exception as e :
104107 self .logger .error (f"Failed to get token from command: { str (e )} " )
105-
108+
106109 # 2. Try environment variable
107110 env_var = f"{ service .upper ()} _TOKEN"
108111 token = os .environ .get (env_var )
109112 if token :
110113 if self .verbose >= 1 :
111114 self .logger .info (f"Using { service } token from environment variable" )
112115 return token
113-
116+
114117 # 3. Try credentials file
115118 if service in self .credentials and "token" in self .credentials [service ]:
116119 if self .verbose >= 1 :
117120 self .logger .info (f"Using { service } token from credentials file" )
118121 return self .credentials [service ]["token" ]
119-
122+
120123 # Token not found
121124 self .logger .warning (f"No { service } token found" )
122125 return None
123-
126+
124127 def get_organization (self , service : str ) -> Optional [str ]:
125128 """
126129 Get organization or group for a service.
127-
130+
128131 Args:
129132 service: Service name ('github' or 'gitlab')
130-
133+
131134 Returns:
132135 Organization/group name or None if not specified
133136 """
134137 # Standardize service name
135138 service = service .lower ()
136-
139+
137140 # Try environment variable first
138141 env_var = f"{ service .upper ()} _ORGANIZATION"
139142 if service == "gitlab" :
140143 env_var = "GITLAB_GROUP" # GitLab uses "group" terminology
141-
144+
142145 org = os .environ .get (env_var )
143146 if org :
144147 return org
145-
148+
146149 # Try credentials file
147150 if service in self .credentials :
148151 if service == "github" and "organization" in self .credentials [service ]:
149152 return self .credentials [service ]["organization" ]
150153 elif service == "gitlab" and "group" in self .credentials [service ]:
151154 return self .credentials [service ]["group" ]
152-
155+
153156 return None
154-
157+
155158 def get_api_url (self , service : str ) -> str :
156159 """
157160 Get API URL for a service.
158-
161+
159162 Args:
160163 service: Service name ('github' or 'gitlab')
161-
164+
162165 Returns:
163166 API URL for the service
164167 """
@@ -168,69 +171,73 @@ def get_api_url(self, service: str) -> str:
168171 return os .environ .get ("GITLAB_API_URL" , "https://gitlab.com/api/v4" )
169172 else :
170173 return ""
171-
172- def store_credentials (self , service : str , token : str ,
173- organization : Optional [str ] = None ,
174- file_path : Optional [str ] = None ) -> bool :
174+
175+ def store_credentials (
176+ self ,
177+ service : str ,
178+ token : str ,
179+ organization : Optional [str ] = None ,
180+ file_path : Optional [str ] = None ,
181+ ) -> bool :
175182 """
176183 Store credentials in the credentials file.
177-
184+
178185 Args:
179186 service: Service name ('github' or 'gitlab')
180187 token: Authentication token
181188 organization: Organization or group name (optional)
182189 file_path: Path to credentials file (or None to use default)
183-
190+
184191 Returns:
185192 True if stored successfully, False otherwise
186193 """
187194 # Standardize service name
188195 service = service .lower ()
189-
196+
190197 # Load existing credentials if file exists
191198 if file_path :
192199 creds_path = Path (file_path )
193200 else :
194201 creds_path = self .default_creds_path
195202 # Ensure directory exists
196203 creds_path .parent .mkdir (parents = True , exist_ok = True )
197-
204+
198205 # Load existing credentials if file exists
199206 existing_creds = {}
200207 if creds_path .exists ():
201208 try :
202- with open (creds_path , 'r' ) as f :
209+ with open (creds_path , "r" ) as f :
203210 existing_creds = json .load (f )
204211 except Exception as e :
205212 self .logger .error (f"Error loading existing credentials: { str (e )} " )
206213 # Continue with empty credentials
207-
214+
208215 # Update credentials
209216 service_creds = existing_creds .get (service , {})
210217 service_creds ["token" ] = token
211-
218+
212219 if organization :
213220 if service == "github" :
214221 service_creds ["organization" ] = organization
215222 elif service == "gitlab" :
216223 service_creds ["group" ] = organization
217-
224+
218225 existing_creds [service ] = service_creds
219-
226+
220227 # Save credentials
221228 try :
222- with open (creds_path , 'w' ) as f :
229+ with open (creds_path , "w" ) as f :
223230 json .dump (existing_creds , f , indent = 2 )
224-
231+
225232 # Set restrictive permissions
226233 os .chmod (creds_path , 0o600 )
227-
234+
228235 if self .verbose >= 1 :
229236 self .logger .info (f"Stored { service } credentials in { creds_path } " )
230-
237+
231238 # Update in-memory credentials
232239 self .credentials = existing_creds
233-
240+
234241 return True
235242 except Exception as e :
236243 self .logger .error (f"Error storing credentials: { str (e )} " )
0 commit comments