256 URL; False if anyone can access this URL. |
262 URL; False if anyone can access this URL. |
257 admin_only: True if the user must be a logged-in administrator to |
263 admin_only: True if the user must be a logged-in administrator to |
258 access the URL; False if anyone can access the URL. |
264 access the URL; False if anyone can access the URL. |
259 """ |
265 """ |
260 if not isinstance(dispatcher, URLDispatcher): |
266 if not isinstance(dispatcher, URLDispatcher): |
261 raise TypeError, 'dispatcher must be a URLDispatcher sub-class' |
267 raise TypeError('dispatcher must be a URLDispatcher sub-class') |
262 |
268 |
263 if regex.startswith('^') or regex.endswith('$'): |
269 if regex.startswith('^') or regex.endswith('$'): |
264 raise InvalidAppConfigError, 'regex starts with "^" or ends with "$"' |
270 raise InvalidAppConfigError('regex starts with "^" or ends with "$"') |
265 |
271 |
266 adjusted_regex = '^%s$' % regex |
272 adjusted_regex = '^%s$' % regex |
267 |
273 |
268 try: |
274 try: |
269 url_re = re.compile(adjusted_regex) |
275 url_re = re.compile(adjusted_regex) |
270 except re.error, e: |
276 except re.error, e: |
271 raise InvalidAppConfigError, 'regex invalid: %s' % e |
277 raise InvalidAppConfigError('regex invalid: %s' % e) |
272 |
278 |
273 match_tuple = (url_re, dispatcher, path, requires_login, admin_only) |
279 match_tuple = (url_re, dispatcher, path, requires_login, admin_only) |
274 self._url_patterns.append(match_tuple) |
280 self._url_patterns.append(match_tuple) |
275 |
281 |
276 def Match(self, |
282 def Match(self, |
346 Matchers are checked in the order they were supplied to the constructor. |
352 Matchers are checked in the order they were supplied to the constructor. |
347 If no matcher matches, a 404 error will be written to the outfile. The |
353 If no matcher matches, a 404 error will be written to the outfile. The |
348 path variable supplied to this method is ignored. |
354 path variable supplied to this method is ignored. |
349 """ |
355 """ |
350 cookies = ', '.join(headers.getheaders('cookie')) |
356 cookies = ', '.join(headers.getheaders('cookie')) |
351 email, admin = self._get_user_info(cookies) |
357 email, admin, user_id = self._get_user_info(cookies) |
352 |
358 |
353 for matcher in self._url_matchers: |
359 for matcher in self._url_matchers: |
354 dispatcher, matched_path, requires_login, admin_only = matcher.Match(relative_url) |
360 dispatcher, matched_path, requires_login, admin_only = matcher.Match(relative_url) |
355 if dispatcher is None: |
361 if dispatcher is None: |
356 continue |
362 continue |
538 env['CONTENT_TYPE'] = headers.getheader('content-type', |
544 env['CONTENT_TYPE'] = headers.getheader('content-type', |
539 'application/x-www-form-urlencoded') |
545 'application/x-www-form-urlencoded') |
540 env['CONTENT_LENGTH'] = headers.getheader('content-length', '') |
546 env['CONTENT_LENGTH'] = headers.getheader('content-length', '') |
541 |
547 |
542 cookies = ', '.join(headers.getheaders('cookie')) |
548 cookies = ', '.join(headers.getheaders('cookie')) |
543 email, admin = get_user_info(cookies) |
549 email, admin, user_id = get_user_info(cookies) |
544 env['USER_EMAIL'] = email |
550 env['USER_EMAIL'] = email |
|
551 env['USER_ID'] = user_id |
545 if admin: |
552 if admin: |
546 env['USER_IS_ADMIN'] = '1' |
553 env['USER_IS_ADMIN'] = '1' |
547 |
554 |
548 for key in headers: |
555 for key in headers: |
549 if key in _IGNORE_REQUEST_HEADERS: |
556 if key in _IGNORE_REQUEST_HEADERS: |
613 |
620 |
614 |
621 |
615 def FakeUnlink(path): |
622 def FakeUnlink(path): |
616 """Fake version of os.unlink.""" |
623 """Fake version of os.unlink.""" |
617 if os.path.isdir(path): |
624 if os.path.isdir(path): |
618 raise OSError(2, "Is a directory", path) |
625 raise OSError(errno.ENOENT, "Is a directory", path) |
619 else: |
626 else: |
620 raise OSError(1, "Operation not permitted", path) |
627 raise OSError(errno.EPERM, "Operation not permitted", path) |
621 |
628 |
622 |
629 |
623 def FakeReadlink(path): |
630 def FakeReadlink(path): |
624 """Fake version of os.readlink.""" |
631 """Fake version of os.readlink.""" |
625 raise OSError(22, "Invalid argument", path) |
632 raise OSError(errno.EINVAL, "Invalid argument", path) |
626 |
633 |
627 |
634 |
628 def FakeAccess(path, mode): |
635 def FakeAccess(path, mode): |
629 """Fake version of os.access where only reads are supported.""" |
636 """Fake version of os.access where only reads are supported.""" |
630 if not os.path.exists(path) or mode != os.R_OK: |
637 if not os.path.exists(path) or mode != os.R_OK: |
634 |
641 |
635 |
642 |
636 def FakeSetLocale(category, value=None, original_setlocale=locale.setlocale): |
643 def FakeSetLocale(category, value=None, original_setlocale=locale.setlocale): |
637 """Fake version of locale.setlocale that only supports the default.""" |
644 """Fake version of locale.setlocale that only supports the default.""" |
638 if value not in (None, '', 'C', 'POSIX'): |
645 if value not in (None, '', 'C', 'POSIX'): |
639 raise locale.Error, 'locale emulation only supports "C" locale' |
646 raise locale.Error('locale emulation only supports "C" locale') |
640 return original_setlocale(category, 'C') |
647 return original_setlocale(category, 'C') |
|
648 |
|
649 |
|
650 def FakeOpen(file, flags, mode=0777): |
|
651 """Fake version of os.open.""" |
|
652 raise OSError(errno.EPERM, "Operation not permitted", file) |
|
653 |
|
654 |
|
655 def FakeRename(src, dst): |
|
656 """Fake version of os.rename.""" |
|
657 raise OSError(errno.EPERM, "Operation not permitted", src) |
|
658 |
|
659 |
|
660 def FakeUTime(path, times): |
|
661 """Fake version of os.utime.""" |
|
662 raise OSError(errno.EPERM, "Operation not permitted", path) |
|
663 |
|
664 |
|
665 def FakeGetPlatform(): |
|
666 """Fake distutils.util.get_platform on OS/X. Pass-through otherwise.""" |
|
667 if sys.platform == 'darwin': |
|
668 return 'macosx-' |
|
669 else: |
|
670 return distutils.util.get_platform() |
641 |
671 |
642 |
672 |
643 def IsPathInSubdirectories(filename, |
673 def IsPathInSubdirectories(filename, |
644 subdirectories, |
674 subdirectories, |
645 normcase=os.path.normcase): |
675 normcase=os.path.normcase): |
768 os.path.normcase(os.path.abspath(os.path.join( |
813 os.path.normcase(os.path.abspath(os.path.join( |
769 os.path.dirname(os.__file__), 'site-packages', path))) |
814 os.path.dirname(os.__file__), 'site-packages', path))) |
770 for path in [ |
815 for path in [ |
771 |
816 |
772 ]) |
817 ]) |
|
818 |
|
819 ALLOWED_SITE_PACKAGE_FILES = set( |
|
820 os.path.normcase(os.path.abspath(os.path.join( |
|
821 os.path.dirname(os.__file__), 'site-packages', path))) |
|
822 for path in itertools.chain(*[ |
|
823 |
|
824 [os.path.join('Crypto')], |
|
825 GeneratePythonPaths('Crypto', '__init__'), |
|
826 [os.path.join('Crypto', 'Cipher')], |
|
827 GeneratePythonPaths('Crypto', 'Cipher', '__init__'), |
|
828 GeneratePythonPaths('Crypto', 'Cipher', 'AES'), |
|
829 GeneratePythonPaths('Crypto', 'Cipher', 'ARC2'), |
|
830 GeneratePythonPaths('Crypto', 'Cipher', 'ARC4'), |
|
831 GeneratePythonPaths('Crypto', 'Cipher', 'Blowfish'), |
|
832 GeneratePythonPaths('Crypto', 'Cipher', 'CAST'), |
|
833 GeneratePythonPaths('Crypto', 'Cipher', 'DES'), |
|
834 GeneratePythonPaths('Crypto', 'Cipher', 'DES3'), |
|
835 GeneratePythonPaths('Crypto', 'Cipher', 'XOR'), |
|
836 [os.path.join('Crypto', 'Hash')], |
|
837 GeneratePythonPaths('Crypto', 'Hash', '__init__'), |
|
838 GeneratePythonPaths('Crypto', 'Hash', 'HMAC'), |
|
839 os.path.join('Crypto', 'Hash', 'MD2'), |
|
840 os.path.join('Crypto', 'Hash', 'MD4'), |
|
841 GeneratePythonPaths('Crypto', 'Hash', 'MD5'), |
|
842 GeneratePythonPaths('Crypto', 'Hash', 'SHA'), |
|
843 os.path.join('Crypto', 'Hash', 'SHA256'), |
|
844 os.path.join('Crypto', 'Hash', 'RIPEMD'), |
|
845 [os.path.join('Crypto', 'Protocol')], |
|
846 GeneratePythonPaths('Crypto', 'Protocol', '__init__'), |
|
847 GeneratePythonPaths('Crypto', 'Protocol', 'AllOrNothing'), |
|
848 GeneratePythonPaths('Crypto', 'Protocol', 'Chaffing'), |
|
849 [os.path.join('Crypto', 'PublicKey')], |
|
850 GeneratePythonPaths('Crypto', 'PublicKey', '__init__'), |
|
851 GeneratePythonPaths('Crypto', 'PublicKey', 'DSA'), |
|
852 GeneratePythonPaths('Crypto', 'PublicKey', 'ElGamal'), |
|
853 GeneratePythonPaths('Crypto', 'PublicKey', 'RSA'), |
|
854 GeneratePythonPaths('Crypto', 'PublicKey', 'pubkey'), |
|
855 GeneratePythonPaths('Crypto', 'PublicKey', 'qNEW'), |
|
856 [os.path.join('Crypto', 'Util')], |
|
857 GeneratePythonPaths('Crypto', 'Util', '__init__'), |
|
858 GeneratePythonPaths('Crypto', 'Util', 'RFC1751'), |
|
859 GeneratePythonPaths('Crypto', 'Util', 'number'), |
|
860 GeneratePythonPaths('Crypto', 'Util', 'randpool'), |
|
861 ])) |
773 |
862 |
774 _original_file = file |
863 _original_file = file |
775 |
864 |
776 _root_path = None |
865 _root_path = None |
777 _application_paths = None |
866 _application_paths = None |
884 normcase: Used for dependency injection. |
970 normcase: Used for dependency injection. |
885 |
971 |
886 Returns: |
972 Returns: |
887 True if the file is accessible, False otherwise. |
973 True if the file is accessible, False otherwise. |
888 """ |
974 """ |
889 if IsPathInSubdirectories(logical_filename, [FakeFile._root_path], |
975 logical_dirfakefile = logical_filename |
|
976 if os.path.isdir(logical_filename): |
|
977 logical_dirfakefile = os.path.join(logical_filename, 'foo') |
|
978 |
|
979 if IsPathInSubdirectories(logical_dirfakefile, [FakeFile._root_path], |
890 normcase=normcase): |
980 normcase=normcase): |
891 relative_filename = logical_filename[len(FakeFile._root_path):] |
981 relative_filename = logical_dirfakefile[len(FakeFile._root_path):] |
892 |
982 |
893 if (not FakeFile._allow_skipped_files and |
983 if (not FakeFile._allow_skipped_files and |
894 FakeFile._skip_files.match(relative_filename)): |
984 FakeFile._skip_files.match(relative_filename)): |
895 logging.warning('Blocking access to skipped file "%s"', |
985 logging.warning('Blocking access to skipped file "%s"', |
896 logical_filename) |
986 logical_filename) |
902 return False |
992 return False |
903 |
993 |
904 if logical_filename in FakeFile.ALLOWED_FILES: |
994 if logical_filename in FakeFile.ALLOWED_FILES: |
905 return True |
995 return True |
906 |
996 |
907 if IsPathInSubdirectories(logical_filename, |
997 if logical_filename in FakeFile.ALLOWED_SITE_PACKAGE_FILES: |
|
998 return True |
|
999 |
|
1000 if IsPathInSubdirectories(logical_dirfakefile, |
908 FakeFile.ALLOWED_SITE_PACKAGE_DIRS, |
1001 FakeFile.ALLOWED_SITE_PACKAGE_DIRS, |
909 normcase=normcase): |
1002 normcase=normcase): |
910 return True |
1003 return True |
911 |
1004 |
912 allowed_dirs = FakeFile._application_paths | FakeFile.ALLOWED_DIRS |
1005 allowed_dirs = FakeFile._application_paths | FakeFile.ALLOWED_DIRS |
913 if (IsPathInSubdirectories(logical_filename, |
1006 if (IsPathInSubdirectories(logical_dirfakefile, |
914 allowed_dirs, |
1007 allowed_dirs, |
915 normcase=normcase) and |
1008 normcase=normcase) and |
916 not IsPathInSubdirectories(logical_filename, |
1009 not IsPathInSubdirectories(logical_dirfakefile, |
917 FakeFile.NOT_ALLOWED_DIRS, |
1010 FakeFile.NOT_ALLOWED_DIRS, |
918 normcase=normcase)): |
1011 normcase=normcase)): |
919 return True |
1012 return True |
920 |
1013 |
921 return False |
1014 return False |
1033 if HardenedModulesHook.ENABLE_LOGGING: |
1126 if HardenedModulesHook.ENABLE_LOGGING: |
1034 indent = self._indent_level * ' ' |
1127 indent = self._indent_level * ' ' |
1035 print >>sys.stderr, indent + (message % args) |
1128 print >>sys.stderr, indent + (message % args) |
1036 |
1129 |
1037 _WHITE_LIST_C_MODULES = [ |
1130 _WHITE_LIST_C_MODULES = [ |
|
1131 'AES', |
|
1132 'ARC2', |
|
1133 'ARC4', |
|
1134 'Blowfish', |
|
1135 'CAST', |
|
1136 'DES', |
|
1137 'DES3', |
|
1138 'MD2', |
|
1139 'MD4', |
|
1140 'RIPEMD', |
|
1141 'SHA256', |
|
1142 'XOR', |
|
1143 |
|
1144 '_Crypto_Cipher__AES', |
|
1145 '_Crypto_Cipher__ARC2', |
|
1146 '_Crypto_Cipher__ARC4', |
|
1147 '_Crypto_Cipher__Blowfish', |
|
1148 '_Crypto_Cipher__CAST', |
|
1149 '_Crypto_Cipher__DES', |
|
1150 '_Crypto_Cipher__DES3', |
|
1151 '_Crypto_Cipher__XOR', |
|
1152 '_Crypto_Hash__MD2', |
|
1153 '_Crypto_Hash__MD4', |
|
1154 '_Crypto_Hash__RIPEMD', |
|
1155 '_Crypto_Hash__SHA256', |
1038 'array', |
1156 'array', |
1039 'binascii', |
1157 'binascii', |
1040 'bz2', |
1158 'bz2', |
1041 'cmath', |
1159 'cmath', |
1042 'collections', |
1160 'collections', |
1189 'os': { |
1327 'os': { |
1190 'access': FakeAccess, |
1328 'access': FakeAccess, |
1191 'listdir': RestrictedPathFunction(os.listdir), |
1329 'listdir': RestrictedPathFunction(os.listdir), |
1192 |
1330 |
1193 'lstat': RestrictedPathFunction(os.stat), |
1331 'lstat': RestrictedPathFunction(os.stat), |
|
1332 'open': FakeOpen, |
1194 'readlink': FakeReadlink, |
1333 'readlink': FakeReadlink, |
1195 'remove': FakeUnlink, |
1334 'remove': FakeUnlink, |
|
1335 'rename': FakeRename, |
1196 'stat': RestrictedPathFunction(os.stat), |
1336 'stat': RestrictedPathFunction(os.stat), |
1197 'uname': FakeUname, |
1337 'uname': FakeUname, |
1198 'unlink': FakeUnlink, |
1338 'unlink': FakeUnlink, |
1199 'urandom': FakeURandom, |
1339 'urandom': FakeURandom, |
|
1340 'utime': FakeUTime, |
|
1341 }, |
|
1342 |
|
1343 'distutils.util': { |
|
1344 'get_platform': FakeGetPlatform, |
1200 }, |
1345 }, |
1201 } |
1346 } |
1202 |
1347 |
1203 _ENABLED_FILE_TYPES = ( |
1348 _ENABLED_FILE_TYPES = ( |
1204 imp.PKG_DIRECTORY, |
1349 imp.PKG_DIRECTORY, |
1481 elif submodule_fullname == 'cPickle': |
1626 elif submodule_fullname == 'cPickle': |
1482 module.__dict__.update(self._pickle.__dict__) |
1627 module.__dict__.update(self._pickle.__dict__) |
1483 module.__name__ = 'cPickle' |
1628 module.__name__ = 'cPickle' |
1484 elif submodule_fullname == 'os': |
1629 elif submodule_fullname == 'os': |
1485 module.__dict__.update(self._os.__dict__) |
1630 module.__dict__.update(self._os.__dict__) |
1486 self._module_dict['os.path'] = module.path |
|
1487 elif self.StubModuleExists(submodule_fullname): |
1631 elif self.StubModuleExists(submodule_fullname): |
1488 module = self.ImportStubModule(submodule_fullname) |
1632 module = self.ImportStubModule(submodule_fullname) |
1489 else: |
1633 else: |
1490 source_file, pathname, description = self.FindModuleRestricted(submodule, submodule_fullname, search_path) |
1634 source_file, pathname, description = self.FindModuleRestricted(submodule, submodule_fullname, search_path) |
1491 module = self.LoadModuleRestricted(submodule_fullname, |
1635 module = self.LoadModuleRestricted(submodule_fullname, |
3011 smtp_user: SMTP user. |
3161 smtp_user: SMTP user. |
3012 smtp_password: SMTP password. |
3162 smtp_password: SMTP password. |
3013 enable_sendmail: Whether to use sendmail as an alternative to SMTP. |
3163 enable_sendmail: Whether to use sendmail as an alternative to SMTP. |
3014 show_mail_body: Whether to log the body of emails. |
3164 show_mail_body: Whether to log the body of emails. |
3015 remove: Used for dependency injection. |
3165 remove: Used for dependency injection. |
|
3166 trusted: True if this app can access data belonging to other apps. This |
|
3167 behavior is different from the real app server and should be left False |
|
3168 except for advanced uses of dev_appserver. |
3016 """ |
3169 """ |
3017 login_url = config['login_url'] |
3170 login_url = config['login_url'] |
3018 datastore_path = config['datastore_path'] |
3171 datastore_path = config['datastore_path'] |
3019 history_path = config['history_path'] |
3172 history_path = config['history_path'] |
3020 clear_datastore = config['clear_datastore'] |
3173 clear_datastore = config['clear_datastore'] |
3024 smtp_user = config.get('smtp_user', '') |
3177 smtp_user = config.get('smtp_user', '') |
3025 smtp_password = config.get('smtp_password', '') |
3178 smtp_password = config.get('smtp_password', '') |
3026 enable_sendmail = config.get('enable_sendmail', False) |
3179 enable_sendmail = config.get('enable_sendmail', False) |
3027 show_mail_body = config.get('show_mail_body', False) |
3180 show_mail_body = config.get('show_mail_body', False) |
3028 remove = config.get('remove', os.remove) |
3181 remove = config.get('remove', os.remove) |
|
3182 trusted = config.get('trusted', False) |
3029 |
3183 |
3030 os.environ['APPLICATION_ID'] = app_id |
3184 os.environ['APPLICATION_ID'] = app_id |
3031 |
3185 |
3032 if clear_datastore: |
3186 if clear_datastore: |
3033 for path in (datastore_path, history_path): |
3187 for path in (datastore_path, history_path): |
3039 logging.warning('Removing file failed: %s', e) |
3193 logging.warning('Removing file failed: %s', e) |
3040 |
3194 |
3041 apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() |
3195 apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() |
3042 |
3196 |
3043 datastore = datastore_file_stub.DatastoreFileStub( |
3197 datastore = datastore_file_stub.DatastoreFileStub( |
3044 app_id, datastore_path, history_path, require_indexes=require_indexes) |
3198 app_id, datastore_path, history_path, require_indexes=require_indexes, |
|
3199 trusted=trusted) |
3045 apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore) |
3200 apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore) |
3046 |
3201 |
3047 fixed_login_url = '%s?%s=%%s' % (login_url, |
3202 fixed_login_url = '%s?%s=%%s' % (login_url, |
3048 dev_appserver_login.CONTINUE_PARAM) |
3203 dev_appserver_login.CONTINUE_PARAM) |
3049 fixed_logout_url = '%s&%s' % (fixed_login_url, |
3204 fixed_logout_url = '%s&%s' % (fixed_login_url, |