18
18
19
19
from requests import Request , Session
20
20
from requests .exceptions import HTTPError
21
- import os , json
21
+ from lxml import html
22
+ import os , json , platform
22
23
23
24
24
25
@register_target ('bb' , 'bitbucket' )
@@ -30,11 +31,16 @@ def __init__(self, *args, **kwarg):
30
31
super (BitbucketService , self ).__init__ (* args , ** kwarg )
31
32
32
33
def connect (self ):
33
- if not self ._privatekey :
34
+ if self ._privatekey and ':' in self ._privatekey :
35
+ login , password = self ._privatekey .split (':' )
36
+ else :
37
+ login = self ._username
38
+ password = self ._privatekey
39
+
40
+ if not login or not password :
34
41
raise ConnectionError ('Could not connect to BitBucket. Please configure .gitconfig with your bitbucket credentials.' )
35
- if not ':' in self ._privatekey :
36
- raise ConnectionError ('Could not connect to BitBucket. Please setup your private key with login:password' )
37
- auth = BasicAuthenticator (
* self .
_privatekey .
split (
':' )
+ [
'[email protected] ' ])
42
+
43
+ auth = BasicAuthenticator (
login ,
password ,
'[email protected] ' )
38
44
self .bb .client .config = auth
39
45
self .bb .client .session = self .bb .client .config .session = auth .start_http_session (self .bb .client .session )
40
46
try :
@@ -353,8 +359,96 @@ def request_fetch(self, user, repo, request, pull=False):
353
359
354
360
@classmethod
355
361
def get_auth_token (cls , login , password , prompt = None ):
356
- log .warn ("/!\\ Due to API limitations, the bitbucket login/password is stored as plaintext in configuration." )
357
- return "{}:{}" .format (login , password )
362
+ session = Session ()
363
+
364
+ key_name = 'git-repo@{}' .format (platform .node ())
365
+
366
+ # get login page
367
+ log .info ('» Login to bitbucket…' )
368
+
369
+ login_url = "https://bitbucket.org/account/signin/?next=/" .format (login )
370
+
371
+ session .headers .update ({
372
+ "User-Agent" :"Mozilla/5.0 (X11; Linux x86_64) " \
373
+ "AppleWebKit/537.36 (KHTML, like Gecko) " \
374
+ "Chrome/52.0.2743.82 Safari/537.36"
375
+ })
376
+
377
+ result = session .get (login_url )
378
+ tree = html .fromstring (result .text )
379
+
380
+ # extract CSRF token
381
+
382
+ authenticity_token = list (set (tree .xpath ("//input[@name='csrfmiddlewaretoken']/@value" )))[0 ]
383
+
384
+ # do login
385
+
386
+ payload = {
387
+ 'username' : login ,
388
+ 'password' : password ,
389
+ 'csrfmiddlewaretoken' : authenticity_token
390
+ }
391
+
392
+ result = session .post (
393
+ login_url ,
394
+ data = payload ,
395
+ headers = dict (referer = login_url )
396
+ )
397
+ tree = html .fromstring (result .text )
398
+
399
+ # extract username
400
+
401
+ try :
402
+ username = json .loads (tree .xpath ('//meta/@data-current-user' )[0 ])['username' ]
403
+ except KeyError :
404
+ raise ResourceNotFoundError ('Invalid login. Please make sure you\' re using your bitbucket email address as username!' )
405
+
406
+ app_password_url = 'https://bitbucket.org/account/user/{}/app-passwords/new' .format (username )
407
+
408
+ # load app password page
409
+ log .info ('» Generating app password…' )
410
+
411
+ result = session .get (
412
+ app_password_url ,
413
+ headers = dict (referer = app_password_url )
414
+ )
415
+ tree = html .fromstring (result .content )
416
+
417
+ if 'git-repo@{}' .format (platform .node ()) in result :
418
+ log .warn ("A duplicate key is being created!" )
419
+
420
+ # generate app password
421
+ log .info ('» App password is setup with following scopes:' )
422
+ log .info ('» account, team, project:write, repository:admin, repository:delete' )
423
+ log .info ('» pullrequest:write, snippet, snippet:write' )
424
+
425
+ authenticity_token = list (set (tree .xpath ("//input[@name='csrfmiddlewaretoken']/@value" )))[0 ]
426
+
427
+ payload = dict (
428
+ name = key_name ,
429
+ scope = ['account' ,
430
+ 'team' ,
431
+ 'project' ,
432
+ 'project:write' ,
433
+ 'repository' ,
434
+ 'pullrequest:write' ,
435
+ 'repository:admin' ,
436
+ 'repository:delete' ,
437
+ 'snippet' ,
438
+ 'snippet:write'
439
+ ],
440
+ csrfmiddlewaretoken = authenticity_token
441
+ )
442
+ result = session .post (
443
+ app_password_url ,
444
+ data = payload ,
445
+ headers = dict (referer = app_password_url )
446
+ )
447
+ tree = html .fromstring (result .content )
448
+
449
+ password = json .loads (tree .xpath ('//section/@data-app-password' )[0 ])['password' ]
450
+
451
+ return password , username
358
452
359
453
@property
360
454
def user (self ):
@@ -364,4 +458,3 @@ def user(self):
364
458
except (HTTPError , AttributeError ) as err :
365
459
raise ResourceError ("Couldn't find the current user: {}" .format (err )) from err
366
460
367
-
0 commit comments