Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/decorators.py: 43%

49 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-02-07 19:28 +0000

1import typing as t 

2 

3from viur.core.module import Method 

4 

5__all__ = [ 

6 "access", 

7 "exposed", 

8 "force_post", 

9 "force_ssl", 

10 "internal_exposed", 

11 "skey", 

12 "cors", 

13] 

14 

15 

16def exposed(func: t.Callable) -> Method: 

17 """ 

18 Decorator, which marks a function as exposed. 

19 

20 Only exposed functions are callable by http-requests. 

21 Can optionally receive a dict of language->translated name to make that function 

22 available under different names 

23 """ 

24 if isinstance(func, dict): 24 ↛ 25line 24 didn't jump to line 25 because the condition on line 24 was never true

25 seo_language_map = func 

26 

27 # We received said dictionary: 

28 def expose_with_translations(func: t.Callable) -> Method: 

29 func = Method.ensure(func) 

30 func.exposed = True 

31 func.seo_language_map = seo_language_map 

32 return func 

33 

34 return expose_with_translations 

35 

36 func = Method.ensure(func) 

37 func.exposed = True 

38 return func 

39 

40 

41def internal_exposed(func: t.Callable) -> Method: 

42 """ 

43 Decorator, which marks a function as internal exposed. 

44 """ 

45 func = Method.ensure(func) 

46 func.exposed = False 

47 return func 

48 

49 

50def force_ssl(func: t.Callable) -> Method: 

51 """ 

52 Decorator, which enforces usage of an encrypted channel for a given resource. 

53 Has no effect on development-servers. 

54 """ 

55 func = Method.ensure(func) 

56 func.ssl = True 

57 return func 

58 

59 

60def force_post(func: t.Callable) -> Method: 

61 """ 

62 Decorator, which enforces usage of a http post request. 

63 """ 

64 func = Method.ensure(func) 

65 func.methods = ("POST",) 

66 return func 

67 

68 

69def access( 

70 *access: str | list[str] | tuple[str] | set[str] | t.Callable, 

71 offer_login: bool | str = False, 

72 message: str | None = None, 

73) -> t.Callable: 

74 """ 

75 Decorator, which performs an authentication and authorization check primarily based on the current user's access, 

76 which is defined via the `UserSkel.access`-bone. Additionally, a callable for individual access checking can be 

77 provided. 

78 

79 In case no user is logged in, the decorator enforces to raise an HTTP error 401 - Unauthorized in case no user is 

80 logged in, otherwise it returns an HTTP error 403 - Forbidden when the specified access parameters prohibit to call 

81 the decorated method. 

82 

83 :params access: Access configuration, either names of access rights or a callable for verification. 

84 :params offer_login: Offers a way to login; Either set it to True, to automatically redirect to /user/login, 

85 or set it to any other URL. 

86 :params message: A custom message to be printed when access is denied or unauthorized. 

87 

88 To check on authenticated users with the access "root" or ("admin" and "file-edit") or "maintainer" use the 

89 decorator like this: 

90 

91 .. code-block:: python 

92 from viur.core.decorators import access 

93 @access("root", ["admin", "file-edit"], ["maintainer"]) 

94 def my_method(self): 

95 return "You're allowed!" 

96 

97 Furthermore, instead of a list/tuple/set/str, a callable can be provided which performs custom access checking, 

98 and directly is checked on True for access grant. 

99 """ 

100 access_config = locals() 

101 

102 def decorator(func): 

103 meth = Method.ensure(func) 

104 meth.access = access_config 

105 return meth 

106 

107 return decorator 

108 

109 

110def skey( 

111 func: t.Callable = None, 

112 *, 

113 allow_empty: bool | list[str] | tuple[str] | t.Callable = False, 

114 forward_payload: str | None = None, 

115 message: str = None, 

116 name: str = "skey", 

117 validate: t.Callable | None = None, 

118 **extra_kwargs: dict, 

119) -> Method: 

120 """ 

121 Decorator, which configures an exposed method for requiring a CSRF-security-key. 

122 The decorator enforces a raise of HTTP error 406 - Precondition failed in case the security-key is not provided 

123 or became invalid. 

124 

125 :param allow_empty: Allows to call the method without a security-key when no other parameters where provided. 

126 This can also be a tuple or list of keys which are being ignored, or a callable taking args and kwargs, and 

127 programmatically decide whether security-key is required or not. 

128 :param forward_payload: Forwards the extracted payload of the security-key to the method under the key specified 

129 here as a value in kwargs. 

130 :param message: Allows to specify a custom error message in case a HTTP 406 is raised. 

131 :param name: Defaults to "skey", but allows also for another name passed to the method. 

132 :param validate: Allows to specify a Callable used to further evaluate the payload of the security-key. 

133 Security-keys can be equipped with further data, see the securitykey-module for details. 

134 :param extra_kwargs: Any provided extra_kwargs are being passed to securitykey.validate as kwargs. 

135 """ 

136 skey_config = locals() 

137 

138 def decorator(func): 

139 meth = Method.ensure(func) 

140 meth.skey = skey_config 

141 return meth 

142 

143 if func is None: 143 ↛ 146line 143 didn't jump to line 146 because the condition on line 143 was always true

144 return decorator 

145 

146 return decorator(func) 

147 

148 

149def cors( 

150 allow_headers: t.Iterable[str] = (), 

151) -> t.Callable: 

152 """Add additional CORS setting for a decorated :meth:`exposed` method.""" 

153 def decorator(func): 

154 meth = Method.ensure(func) 

155 meth.cors_allow_headers = allow_headers 

156 return meth 

157 

158 return decorator