Coverage for webapp/authentication.py: 97%

34 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-15 22:43 +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# keys for session data that should be cleared on auth refresh 

23SESSION_AUTH_KEYS = [ 

24 "macaroons", 

25 "macaroon_root", 

26 "macaroon_discharge", 

27 "publisher", 

28 "developer_token", 

29 "exchanged_developer_token", 

30 "csrf_token", 

31] 

32 

33# keys for session data that should NOT be cleared on auth refresh 

34SESSION_INTEGRATION_KEYS = [ 

35 "github_auth_secret", 

36] 

37 

38SESSION_DATA_KEYS = SESSION_AUTH_KEYS + SESSION_INTEGRATION_KEYS 

39 

40 

41def get_authorization_header(root, discharge): 

42 """ 

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

44 """ 

45 

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

47 Macaroon.deserialize(discharge) 

48 ) 

49 

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

51 

52 

53def get_publishergw_authorization_header(developer_token): 

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

55 

56 

57def is_authenticated(session): 

58 """ 

59 Checks if the user is authenticated from the session 

60 Returns True if the user is authenticated 

61 """ 

62 return ( 

63 "publisher" in session 

64 and "macaroon_discharge" in session 

65 and "macaroon_root" in session 

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

67 

68 

69def empty_session(session): 

70 """ 

71 Empty the session, used to logout. 

72 """ 

73 for key in SESSION_DATA_KEYS: 

74 session.pop(key, None) 

75 

76 

77def reset_auth_session(session): 

78 """ 

79 Clear Snapcraft auth keys while preserving integration tokens 

80 (e.g. github_auth_secret). Used when re-authentication is needed 

81 but third-party tokens are still valid. 

82 """ 

83 for key in SESSION_AUTH_KEYS: 

84 session.pop(key, None) 

85 

86 

87def get_caveat_id(root): 

88 """ 

89 Returns the caveat_id generated by the SSO 

90 """ 

91 location = urlparse(LOGIN_URL).hostname 

92 (caveat,) = [ 

93 c 

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

95 if c.location == location 

96 ] 

97 

98 return caveat.caveat_id 

99 

100 

101def request_macaroon(): 

102 """ 

103 Request a macaroon from dashboard. 

104 Returns the macaroon. 

105 """ 

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

107 

108 return response["macaroon"] 

109 

110 

111def get_refreshed_discharge(discharge): 

112 """ 

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

114 Returns the new discharge macaroon. 

115 """ 

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

117 

118 return response["discharge_macaroon"] 

119 

120 

121def is_macaroon_expired(headers): 

122 """ 

123 Returns True if the macaroon needs to be refreshed from 

124 the header response. 

125 """ 

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