Coverage for webapp / authentication.py: 97%

29 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2025-12-29 22:06 +0000

1import os 

2 

3from urllib.parse import urlparse 

4 

5from pymacaroons import Macaroon 

6from webapp.api import sso 

7 

8LOGIN_URL = os.getenv("LOGIN_URL", "https://login.ubuntu.com") 

9 

10PERMISSIONS = [ 

11 "edit_account", 

12 "package_access", 

13 "package_manage", 

14 "package_metrics", 

15 "package_register", 

16 "package_release", 

17 "package_update", 

18 "package_upload_request", 

19 "store_admin", 

20] 

21 

22 

23SESSION_DATA_KEYS = [ 

24 "macaroons", 

25 "macaroon_root", 

26 "macaroon_discharge", 

27 "publisher", 

28 "github_auth_secret", 

29 "developer_token", 

30 "exchanged_developer_token", 

31 "csrf_token", 

32] # keys for data stored in the session that should be cleared on logout 

33 

34 

35def get_authorization_header(root, discharge): 

36 """ 

37 Bind root and discharge macaroons and return the authorization header. 

38 """ 

39 

40 bound = Macaroon.deserialize(root).prepare_for_request( 

41 Macaroon.deserialize(discharge) 

42 ) 

43 

44 return "macaroon root={}, discharge={}".format(root, bound.serialize()) 

45 

46 

47def get_publishergw_authorization_header(developer_token): 

48 return {"Authorization ": f"Macaroon {developer_token}"} 

49 

50 

51def is_authenticated(session): 

52 """ 

53 Checks if the user is authenticated from the session 

54 Returns True if the user is authenticated 

55 """ 

56 return ( 

57 "publisher" in session 

58 and "macaroon_discharge" in session 

59 and "macaroon_root" in session 

60 ) or ("publisher" in session and "macaroons" in session) 

61 

62 

63def empty_session(session): 

64 """ 

65 Empty the session, used to logout. 

66 """ 

67 for key in SESSION_DATA_KEYS: 

68 session.pop(key, None) 

69 

70 

71def get_caveat_id(root): 

72 """ 

73 Returns the caveat_id generated by the SSO 

74 """ 

75 location = urlparse(LOGIN_URL).hostname 

76 (caveat,) = [ 

77 c 

78 for c in Macaroon.deserialize(root).third_party_caveats() 

79 if c.location == location 

80 ] 

81 

82 return caveat.caveat_id 

83 

84 

85def request_macaroon(): 

86 """ 

87 Request a macaroon from dashboard. 

88 Returns the macaroon. 

89 """ 

90 response = sso.post_macaroon({"permissions": PERMISSIONS}) 

91 

92 return response["macaroon"] 

93 

94 

95def get_refreshed_discharge(discharge): 

96 """ 

97 Get a refresh macaroon if the macaroon is not valid anymore. 

98 Returns the new discharge macaroon. 

99 """ 

100 response = sso.get_refreshed_discharge({"discharge_macaroon": discharge}) 

101 

102 return response["discharge_macaroon"] 

103 

104 

105def is_macaroon_expired(headers): 

106 """ 

107 Returns True if the macaroon needs to be refreshed from 

108 the header response. 

109 """ 

110 return headers.get("WWW-Authenticate") == ("Macaroon needs_refresh=1")