68 |
68 |
69 DEF_LOGOUT_MSG_FMT = ugettext( |
69 DEF_LOGOUT_MSG_FMT = ugettext( |
70 'Please <a href="%(sign_out)s">sign out</a> in order to view this page') |
70 'Please <a href="%(sign_out)s">sign out</a> in order to view this page') |
71 |
71 |
72 |
72 |
73 def checkAccess(access_type, request, rights, args=None, kwargs=None): |
73 def checkAccess(access_type, rights, kwargs=None): |
74 """Runs all the defined checks for the specified type. |
74 """Runs all the defined checks for the specified type. |
75 |
75 |
76 Args: |
76 Args: |
77 access_type: the type of request (such as 'list' or 'edit') |
77 access_type: the type of request (such as 'list' or 'edit') |
78 request: the Django request object |
|
79 rights: a dictionary containing access check functions |
78 rights: a dictionary containing access check functions |
|
79 kwargs: a dictionary with django's arguments |
80 |
80 |
81 Rights usage: |
81 Rights usage: |
82 The rights dictionary is used to check if the current user is allowed |
82 The rights dictionary is used to check if the current user is allowed |
83 to view the page specified. The functions defined in this dictionary |
83 to view the page specified. The functions defined in this dictionary |
84 are always called with the django request object as argument. On any |
84 are always called with the provided kwargs dictionary as argument. On any |
85 request, regardless of what type, the functions in the 'any_access' value |
85 request, regardless of what type, the functions in the 'any_access' value |
86 are called. If the specified type is not in the rights dictionary, all |
86 are called. If the specified type is not in the rights dictionary, all |
87 the functions in the 'unspecified' value are called. When the specified |
87 the functions in the 'unspecified' value are called. When the specified |
88 type _is_ in the rights dictionary, all the functions in that access_type's |
88 type _is_ in the rights dictionary, all the functions in that access_type's |
89 value are called. |
89 value are called. |
90 |
|
91 Returns: |
|
92 True: If all the required access checks have been made successfully |
|
93 False: If a check failed, in this case self._response will contain |
|
94 the response provided by the failed access check. |
|
95 """ |
90 """ |
96 |
91 |
97 # Call each access checker |
92 # Call each access checker |
98 for check in rights['any_access']: |
93 for check in rights['any_access']: |
99 check(request, args, kwargs) |
94 check(kwargs) |
100 |
95 |
101 if access_type not in rights: |
96 if access_type not in rights: |
102 for check in rights['unspecified']: |
97 for check in rights['unspecified']: |
103 # No checks defined, so do the 'generic' checks and bail out |
98 # No checks defined, so do the 'generic' checks and bail out |
104 check(request, args, kwargs) |
99 check(kwargs) |
105 return |
100 return |
106 |
101 |
107 for check in rights[access_type]: |
102 for check in rights[access_type]: |
108 check(request, args, kwargs) |
103 check(kwargs) |
109 |
104 |
110 |
105 |
111 def allow(request, args, kwargs): |
106 def allow(kwargs): |
112 """Never raises an alternate HTTP response. (an access no-op, basically). |
107 """Never raises an alternate HTTP response. (an access no-op, basically). |
113 |
108 |
114 Args: |
109 Args: |
115 request: a Django HTTP request |
110 kwargs: a dictionary with django's arguments |
116 """ |
111 """ |
|
112 |
117 return |
113 return |
118 |
114 |
119 |
115 |
120 def deny(request, args, kwargs): |
116 def deny(kwargs): |
121 """Always raises an alternate HTTP response. |
117 """Always raises an alternate HTTP response. |
122 |
118 |
123 Args: |
119 Args: |
124 request: a Django HTTP request |
120 kwargs: a dictionary with django's arguments |
125 |
121 |
126 Raises: |
122 Raises: |
127 always raises AccessViolationResponse if called |
123 always raises AccessViolationResponse if called |
128 """ |
124 """ |
129 |
125 |
130 import soc.views.helper.responses |
126 import soc.views.helper.responses |
131 |
127 |
132 if kwargs.get('SIDEBAR_CALLING', False): |
128 kwargs.get('context', {}) |
133 context = {} |
|
134 else: |
|
135 context = soc.views.helper.responses.getUniversalContext(request) |
|
136 |
|
137 context['title'] = 'Access denied' |
129 context['title'] = 'Access denied' |
138 |
130 |
139 raise out_of_band.AccessViolation(DEF_PAGE_DENIED_MSG, context=context) |
131 raise out_of_band.AccessViolation(DEF_PAGE_DENIED_MSG, context=context) |
140 |
132 |
141 |
133 |
142 def checkIsLoggedIn(request, args, kwargs): |
134 def checkIsLoggedIn(kwargs): |
143 """Raises an alternate HTTP response if Google Account is not logged in. |
135 """Raises an alternate HTTP response if Google Account is not logged in. |
144 |
136 |
145 Args: |
137 Args: |
146 request: a Django HTTP request |
138 kwargs: a dictionary with django's arguments |
147 |
139 |
148 Raises: |
140 Raises: |
149 AccessViolationResponse: |
141 AccessViolationResponse: |
150 * if no Google Account is even logged in |
142 * if no Google Account is even logged in |
151 """ |
143 """ |
|
144 |
152 if users.get_current_user(): |
145 if users.get_current_user(): |
153 return |
146 return |
154 |
147 |
155 raise out_of_band.LoginRequest() |
148 raise out_of_band.LoginRequest() |
156 |
149 |
157 |
150 |
158 def checkNotLoggedIn(request, args, kwargs): |
151 def checkNotLoggedIn(kwargs): |
159 """Raises an alternate HTTP response if Google Account is logged in. |
152 """Raises an alternate HTTP response if Google Account is logged in. |
160 |
153 |
161 Args: |
154 Args: |
162 request: a Django HTTP request |
155 kwargs: a dictionary with django's arguments |
163 |
156 |
164 Raises: |
157 Raises: |
165 AccessViolationResponse: |
158 AccessViolationResponse: |
166 * if a Google Account is currently logged in |
159 * if a Google Account is currently logged in |
167 """ |
160 """ |
|
161 |
168 if not users.get_current_user(): |
162 if not users.get_current_user(): |
169 return |
163 return |
170 |
164 |
171 raise out_of_band.LoginRequest(message_fmt=DEF_LOGOUT_MSG_FMT) |
165 raise out_of_band.LoginRequest(message_fmt=DEF_LOGOUT_MSG_FMT) |
172 |
166 |
173 |
167 |
174 def checkIsUser(request, args, kwargs): |
168 def checkIsUser(kwargs): |
175 """Raises an alternate HTTP response if Google Account has no User entity. |
169 """Raises an alternate HTTP response if Google Account has no User entity. |
176 |
170 |
177 Args: |
171 Args: |
178 request: a Django HTTP request |
172 kwargs: a dictionary with django's arguments |
179 |
173 |
180 Raises: |
174 Raises: |
181 AccessViolationResponse: |
175 AccessViolationResponse: |
182 * if no User exists for the logged-in Google Account, or |
176 * if no User exists for the logged-in Google Account, or |
183 * if no Google Account is logged in at all |
177 * if no Google Account is logged in at all |
184 """ |
178 """ |
185 checkIsLoggedIn(request, args, kwargs) |
179 |
186 |
180 checkIsLoggedIn(kwargs) |
187 user = user_logic.getForFields({'account': users.get_current_user()}, |
181 |
188 unique=True) |
182 user = user_logic.getForCurrentAccount() |
189 |
183 |
190 if user: |
184 if user: |
191 return |
185 return |
192 |
186 |
193 raise out_of_band.LoginRequest(message_fmt=DEF_NO_USER_LOGIN_MSG_FMT) |
187 raise out_of_band.LoginRequest(message_fmt=DEF_NO_USER_LOGIN_MSG_FMT) |
194 |
188 |
195 |
189 |
196 def checkAgreesToSiteToS(request, args, kwargs): |
190 def checkAgreesToSiteToS(kwargs): |
197 """Raises an alternate HTTP response if User has not agreed to site-wide ToS. |
191 """Raises an alternate HTTP response if User has not agreed to site-wide ToS. |
198 |
192 |
199 Args: |
193 Args: |
200 request: a Django HTTP request |
194 kwargs: a dictionary with django's arguments |
201 |
195 |
202 Raises: |
196 Raises: |
203 AccessViolationResponse: |
197 AccessViolationResponse: |
204 * if User has not agreed to the site-wide ToS, or |
198 * if User has not agreed to the site-wide ToS, or |
205 * if no User exists for the logged-in Google Account, or |
199 * if no User exists for the logged-in Google Account, or |
206 * if no Google Account is logged in at all |
200 * if no Google Account is logged in at all |
207 """ |
201 """ |
208 checkIsUser(request, args, kwargs) |
202 |
|
203 checkIsUser(kwargs) |
209 |
204 |
210 user = user_logic.getForCurrentAccount() |
205 user = user_logic.getForCurrentAccount() |
211 |
206 |
212 if user_logic.agreesToSiteToS(user): |
207 if user_logic.agreesToSiteToS(user): |
213 return |
208 return |
246 |
241 |
247 def checkCanCreateFromRequest(role_name): |
242 def checkCanCreateFromRequest(role_name): |
248 """Raises an alternate HTTP response if the specified request does not exist |
243 """Raises an alternate HTTP response if the specified request does not exist |
249 or if it's state is not group_accepted. |
244 or if it's state is not group_accepted. |
250 """ |
245 """ |
251 def wrapper(request, args, kwargs): |
246 |
252 checkAgreesToSiteToS(request, args, kwargs) |
247 def wrapper(kwargs): |
|
248 checkAgreesToSiteToS(kwargs) |
253 |
249 |
254 user_entity = user_logic.getForCurrentAccount() |
250 user_entity = user_logic.getForCurrentAccount() |
255 |
251 |
256 if user_entity.link_id != kwargs['link_id']: |
252 if user_entity.link_id != kwargs['link_id']: |
257 deny(request, args, kwargs) |
253 deny(kwargs) |
258 |
254 |
259 fields = {'link_id': kwargs['link_id'], |
255 fields = {'link_id': kwargs['link_id'], |
260 'scope_path': kwargs['scope_path'], |
256 'scope_path': kwargs['scope_path'], |
261 'role': role_name} |
257 'role': role_name} |
262 |
258 |
263 request_entity = request_logic.getFromFieldsOr404(**fields) |
259 request_entity = request_logic.getFromFieldsOr404(**fields) |
264 |
260 |
265 if request_entity.state != 'group_accepted': |
261 if request_entity.state != 'group_accepted': |
266 # TODO tell the user that this request has not been accepted yet |
262 # TODO tell the user that this request has not been accepted yet |
267 deny(request, args, kwargs) |
263 deny(kwargs) |
268 |
264 |
269 return |
265 return |
|
266 |
270 return wrapper |
267 return wrapper |
271 |
268 |
272 |
269 |
273 def checkCanProcessRequest(role_name): |
270 def checkCanProcessRequest(role_name): |
274 """Raises an alternate HTTP response if the specified request does not exist |
271 """Raises an alternate HTTP response if the specified request does not exist |
275 or if it's state is completed or denied. |
272 or if it's state is completed or denied. |
276 """ |
273 """ |
277 def wrapper(request, args, kwargs): |
274 |
|
275 def wrapper(kwargs): |
278 |
276 |
279 fields = {'link_id': kwargs['link_id'], |
277 fields = {'link_id': kwargs['link_id'], |
280 'scope_path': kwargs['scope_path'], |
278 'scope_path': kwargs['scope_path'], |
281 'role': role_name} |
279 'role': role_name} |
282 |
280 |
283 request_entity = request_logic.getFromFieldsOr404(**fields) |
281 request_entity = request_logic.getFromFieldsOr404(**fields) |
284 |
282 |
285 if request_entity.state in ['completed', 'denied']: |
283 if request_entity.state in ['completed', 'denied']: |
286 # TODO tell the user that this request has been processed |
284 # TODO tell the user that this request has been processed |
287 deny(request, args, kwargs) |
285 deny(kwargs) |
288 |
286 |
289 return |
287 return |
|
288 |
290 return wrapper |
289 return wrapper |
291 |
290 |
292 |
291 |
293 def checkIsMyGroupAcceptedRequest(request, args, kwargs): |
292 def checkIsMyGroupAcceptedRequest(kwargs): |
294 """Raises an alternate HTTP response if the specified request does not exist |
293 """Raises an alternate HTTP response if the specified request does not exist |
295 or if it's state is not group_accepted. |
294 or if it's state is not group_accepted. |
296 """ |
295 """ |
297 checkAgreesToSiteToS(request, args, kwargs) |
296 |
|
297 checkAgreesToSiteToS(kwargs) |
298 |
298 |
299 user_entity = user_logic.getForCurrentAccount() |
299 user_entity = user_logic.getForCurrentAccount() |
300 |
300 |
301 if user_entity.link_id != kwargs['link_id']: |
301 if user_entity.link_id != kwargs['link_id']: |
302 # not the current user's request |
302 # not the current user's request |
303 return deny(request, args, kwargs) |
303 return deny(kwargs) |
304 |
304 |
305 fields = {'link_id': kwargs['link_id'], |
305 fields = {'link_id': kwargs['link_id'], |
306 'scope_path': kwargs['scope_path'], |
306 'scope_path': kwargs['scope_path'], |
307 'role': kwargs['role']} |
307 'role': kwargs['role']} |
308 |
308 |
309 request_entity = request_logic.getForFields(fields, unique=True) |
309 request_entity = request_logic.getForFields(fields, unique=True) |
310 |
310 |
311 if not request_entity: |
311 if not request_entity: |
312 # TODO return 404 |
312 # TODO return 404 |
313 return deny(request, args, kwargs) |
313 return deny(kwargs) |
314 |
314 |
315 if request_entity.state != 'group_accepted': |
315 if request_entity.state != 'group_accepted': |
316 return deny(request, args, kwargs) |
316 return deny(kwargs) |
317 |
317 |
318 return |
318 return |
319 |
319 |
320 |
320 |
321 def checkIsHost(request, args, kwargs): |
321 def checkIsHost(kwargs): |
322 """Raises an alternate HTTP response if Google Account has no Host entity. |
322 """Raises an alternate HTTP response if Google Account has no Host entity. |
323 |
323 |
324 Args: |
324 Args: |
325 request: a Django HTTP request |
325 request: a Django HTTP request |
326 |
326 |
476 |
475 |
477 if application: |
476 if application: |
478 return |
477 return |
479 |
478 |
480 # TODO(srabbelier) Make this give a proper error message |
479 # TODO(srabbelier) Make this give a proper error message |
481 deny(request, args, kwargs) |
480 deny(kwargs) |
482 |
481 |
483 return wrapper |
482 return wrapper |
484 |
483 |
485 |
484 |
486 def checkIsMyNotification(request, args, kwargs): |
485 def checkIsMyNotification(kwargs): |
487 """Returns an alternate HTTP response if this request is for |
486 """Returns an alternate HTTP response if this request is for |
488 a Notification belonging to the current user. |
487 a Notification belonging to the current user. |
489 |
488 |
490 Args: |
489 Args: |
491 request: a Django HTTP request |
490 kwargs: a dictionary with django's arguments |
492 |
491 |
493 Raises: |
492 Raises: |
494 AccessViolationResponse: if the required authorization is not met |
493 AccessViolationResponse: if the required authorization is not met |
495 |
494 |
496 Returns: |
495 Returns: |
497 None if the current User is allowed to access this Notification. |
496 None if the current User is allowed to access this Notification. |
498 """ |
497 """ |
499 |
498 |
500 try: |
499 try: |
501 # if the current user is a developer we allow access |
500 # if the current user is a developer we allow access |
502 checkIsDeveloper(request, args, kwargs) |
501 checkIsDeveloper(kwargs) |
503 return |
502 return |
504 except out_of_band.Error: |
503 except out_of_band.Error: |
505 pass |
504 pass |
506 |
505 |
507 checkAgreesToSiteToS(request, args, kwargs) |
506 checkAgreesToSiteToS(kwargs) |
508 |
|
509 # Mine the url for params |
|
510 try: |
|
511 callback, args, kwargs = urlresolvers.resolve(request.path) |
|
512 except Exception: |
|
513 deny(request, args, kwargs) |
|
514 |
507 |
515 properties = dicts.filter(kwargs, ['link_id', 'scope_path']) |
508 properties = dicts.filter(kwargs, ['link_id', 'scope_path']) |
516 |
509 |
517 notification = notification_logic.getForFields(properties, unique=True) |
510 notification = notification_logic.getForFields(properties, unique=True) |
518 user = user_logic.getForCurrentAccount() |
511 user = user_logic.getForCurrentAccount() |
539 |
532 |
540 Returns: |
533 Returns: |
541 None if the current User is allowed to access this Application. |
534 None if the current User is allowed to access this Application. |
542 """ |
535 """ |
543 |
536 |
544 def wrapper(request, args, kwargs): |
537 def wrapper(kwargs): |
545 try: |
538 try: |
546 # if the current user is a developer we allow access |
539 # if the current user is a developer we allow access |
547 checkIsDeveloper(request, args, kwargs) |
540 checkIsDeveloper(kwargs) |
548 return |
541 return |
549 except out_of_band.Error: |
542 except out_of_band.Error: |
550 pass |
543 pass |
551 |
544 |
552 checkAgreesToSiteToS(request, args, kwargs) |
545 checkAgreesToSiteToS(kwargs) |
553 |
546 |
554 properties = dicts.filter(kwargs, ['link_id']) |
547 properties = dicts.filter(kwargs, ['link_id']) |
555 |
548 |
556 application = app_logic.logic.getForFields(properties, unique=True) |
549 application = app_logic.logic.getForFields(properties, unique=True) |
557 |
550 |
558 if not application: |
551 if not application: |
559 deny(request, args, kwargs) |
552 deny(kwargs) |
560 |
553 |
561 user = user_logic.getForCurrentAccount() |
554 user = user_logic.getForCurrentAccount() |
562 |
555 |
563 # We need to check to see if the key's are equal since the User |
556 # We need to check to see if the key's are equal since the User |
564 # objects are different and the default __eq__ method does not check |
557 # objects are different and the default __eq__ method does not check |
565 # if the keys are equal (which is what we want). |
558 # if the keys are equal (which is what we want). |
566 if user.key() == application.applicant.key(): |
559 if user.key() == application.applicant.key(): |
567 return None |
560 return None |
568 |
561 |
569 # TODO(srabbelier) Make this give a proper error message |
562 # TODO(srabbelier) Make this give a proper error message |
570 deny(request, args, kwargs) |
563 deny(kwargs) |
571 |
564 |
572 return wrapper |
565 return wrapper |
573 |
566 |
574 |
567 |
575 def checkIsMyActiveRole(role_logic): |
568 def checkIsMyActiveRole(role_logic): |
581 |
574 |
582 Returns: |
575 Returns: |
583 None if the current User has no active role for the given role_logic. |
576 None if the current User has no active role for the given role_logic. |
584 """ |
577 """ |
585 |
578 |
586 def wrapper(request, args, kwargs): |
579 def wrapper(kwargs): |
587 try: |
580 try: |
588 # if the current user is a developer we allow access |
581 # if the current user is a developer we allow access |
589 checkIsDeveloper(request, args, kwargs) |
582 checkIsDeveloper(kwargs) |
590 return |
583 return |
591 except out_of_band.Error: |
584 except out_of_band.Error: |
592 pass |
585 pass |
593 |
586 |
594 user = user_logic.getForCurrentAccount() |
587 user = user_logic.getForCurrentAccount() |
595 |
588 |
596 if not user or user.link_id != kwargs['link_id']: |
589 if not user or user.link_id != kwargs['link_id']: |
597 # not my role |
590 # not my role |
598 deny(request, args, kwargs) |
591 deny(kwargs) |
599 |
592 |
600 fields = {'link_id': kwargs['link_id'], |
593 fields = {'link_id': kwargs['link_id'], |
601 'scope_path': kwargs['scope_path'] |
594 'scope_path': kwargs['scope_path'] |
602 } |
595 } |
603 |
596 |
604 role_entity = role_logic.logic.getForFields(fields, unique=True) |
597 role_entity = role_logic.logic.getForFields(fields, unique=True) |
605 |
598 |
606 if not role_entity: |
599 if not role_entity: |
607 # no role found |
600 # no role found |
608 deny(request, args, kwargs) |
601 deny(kwargs) |
609 |
602 |
610 if role_entity.state == 'active': |
603 if role_entity.state == 'active': |
611 # this role exist and is active |
604 # this role exist and is active |
612 return |
605 return |
613 else: |
606 else: |
614 # this role is not active |
607 # this role is not active |
615 deny(request, args, kwargs) |
608 deny(kwargs) |
616 |
609 |
617 return wrapper |
610 return wrapper |
618 |
611 |
619 |
612 |
620 def checkCanInvite(request, args, kwargs): |
613 def checkCanInvite(kwargs): |
621 """Checks to see if the current user can create an invite. |
614 """Checks to see if the current user can create an invite. |
622 |
615 |
623 Note that if the current url is not in the default 'request' form |
616 Note that if the current url is not in the default 'request' form |
624 this method either deny()s or performs the wrong access check. |
617 this method either deny()s or performs the wrong access check. |
625 |
618 |
627 request: a Django HTTP request |
620 request: a Django HTTP request |
628 """ |
621 """ |
629 |
622 |
630 try: |
623 try: |
631 # if the current user is a developer we allow access |
624 # if the current user is a developer we allow access |
632 checkIsDeveloper(request, args, kwargs) |
625 checkIsDeveloper(kwargs) |
633 return |
626 return |
634 except out_of_band.Error: |
627 except out_of_band.Error: |
635 pass |
628 pass |
636 |
|
637 # Mine the url for params |
|
638 try: |
|
639 callback, args, kwargs = urlresolvers.resolve(request.path) |
|
640 except Exception: |
|
641 deny(request, args, kwargs) |
|
642 |
629 |
643 # Construct a new url by reshufling the kwargs |
630 # Construct a new url by reshufling the kwargs |
644 order = ['role', 'access_type', 'scope_path', 'link_id'] |
631 order = ['role', 'access_type', 'scope_path', 'link_id'] |
645 url_params = dicts.unzip(kwargs, order) |
632 url_params = dicts.unzip(kwargs, order) |
646 url = '/'.join([''] + list(url_params)) |
633 url = '/'.join([''] + list(url_params)) |
647 |
634 |
648 # Mine the reshufled url |
635 # Mine the reshufled url |
649 try: |
636 try: |
650 callback, args, kwargs = urlresolvers.resolve(url) |
637 callback, args, kwargs = urlresolvers.resolve(url) |
651 except Exception: |
638 except Exception: |
652 deny(request, args, kwargs) |
639 deny(kwargs) |
653 |
640 |
654 # Get the everything we need for the access check |
641 # Get the everything we need for the access check |
655 params = callback.im_self.getParams() |
642 params = callback.im_self.getParams() |
656 access_type = kwargs['access_type'] |
643 access_type = kwargs['access_type'] |
657 |
644 |
658 # Perform the access check |
645 # Perform the access check |
659 checkAccess(access_type, request, rights=params['rights']) |
646 checkAccess(access_type, rights=params['rights'], kwargs=kwargs) |
660 |
647 |
661 |
648 |
662 def checkHasPickGetArgs(request, arg, kwargs): |
649 def checkHasPickGetArgs(kwargs): |
663 """Raises an alternate HTTP response if the request misses get args. |
650 """Raises an alternate HTTP response if the request misses get args. |
664 |
651 |
665 Args: |
652 Args: |
666 request: a Django HTTP request |
653 kwargs: a dictionary with django's arguments |
667 |
654 |
668 Raises: |
655 Raises: |
669 AccessViolationResponse: |
656 AccessViolationResponse: |
670 * if continue is not in request.GET |
657 * if continue is not in request.GET |
671 * if field is not in request.GET |
658 * if field is not in request.GET |
672 """ |
659 """ |
673 |
660 |
674 get_args = request.GET |
661 get_args = kwargs.get('GET', {}) |
675 |
662 |
676 if 'continue' in get_args and 'field' in get_args: |
663 if 'continue' in get_args and 'field' in get_args: |
677 return |
664 return |
678 |
665 |
679 #TODO(SRabbelier) inform user that return_url and field are required |
666 #TODO(SRabbelier) inform user that return_url and field are required |
680 deny(request, arg, kwargs) |
667 deny(kwargs) |
681 |
668 |
682 |
669 |
683 def checkIsDocumentPublic(request, args, kwargs): |
670 def checkIsDocumentPublic(kwargs): |
684 """Checks whether a document is public. |
671 """Checks whether a document is public. |
685 |
672 |
686 Args: |
673 Args: |
687 request: a Django HTTP request |
674 kwargs: a dictionary with django's arguments |
688 """ |
675 """ |
689 |
676 |
690 # TODO(srabbelier): A proper check needs to be done to see if the document |
677 # TODO(srabbelier): A proper check needs to be done to see if the document |
691 # is public or not, probably involving analysing it's scope or such. |
678 # is public or not, probably involving analysing it's scope or such. |
692 allow(request, args, kwargs) |
679 allow(kwargs) |