def get(self): """GET method. With no parameters, return the URL to use to submit uploads via multipart/form-data. With mode and key parameters, return status of the previous uploadpkg operation to the calling client. This method actually acts as a helper to the starting and finishing of the uploadpkg post() method. Parameters: mode: optionally, 'success' or 'error' key: optionally, blobstore key that was uploaded """ if not handlers.IsHttps(self): # TODO(user): Does blobstore support https yet? If so, we can # enforce security in app.yaml and not do this check here. return gaeserver.DoMunkiAuth() mode = self.request.get('mode') msg = self.request.get('msg', None) if mode == 'success': self.response.out.write(self.request.get('key')) elif mode == 'error': self.response.set_status(400) self.response.out.write(msg) else: upload_url = blobstore.create_upload_url( '/uploadpkg', gs_bucket_name=util.GetBlobstoreGSBucket()) self.response.out.write(upload_url)
def DoAnyAuth(is_admin=None, require_level=None): """Verify that any form of auth has occured. Includes DoUserAuth and gaeserver.DoMunkiAuth. Args: is_admin: bool, default False, when True, requires that the user is an admin. require_level: int, default None, when defined, requires that a session be at level x. Returns: users.User() object if DoUserAuth succeeded models.AuthSession entity if DoMunkiAuth succeeded Raises: NotAuthenticated: there is no authentication user for this request. IsAdminMismatch: the current user is not an administrator. gaeserver.NotAuthenticated: there is no authentication user for this request. """ # TODO(user): The unexpected return of two different return classes # here can be hard to code around. We should fix this someday if we # start using the return value more frequently, rather than just # calling this as a procedure to cause auth to occur. try: return DoUserAuth(is_admin=is_admin) except IsAdminMismatch: raise except NotAuthenticated: pass # gaeserver.NotAuthenticated will be raised in the case of failure. return gaeserver.DoMunkiAuth(require_level=require_level)
def get(self): """GET.""" logout = self.request.get('logout') session = gaeserver.DoMunkiAuth() if logout: gaeserver.LogoutSession(session)
def post(self): """Returns auth token for get method.""" session = gaeserver.DoMunkiAuth() asd = gaeserver.AuthSessionSimianServer() token = None for s in asd.GetByUuid(session.uuid): if s.level != gaeserver.LEVEL_APPLESUS: continue if asd.IsExpired(s): continue assert s.key().name().startswith('t_') token = s.key().name()[2:] if not token: auth1 = gaeserver.AuthSimianServer() # create new token suitable only for applesus. # original token will be destroyed on postflight. token = auth1.SessionCreateUserAuthToken( session.uuid, level=gaeserver.LEVEL_APPLESUS) munki_header = self.request.headers.get(MUNKI_CLIENT_ID_HEADER_KEY, '') # Also store munki header, which contain OS X version and track. d = { 'cookies': auth.CreateAuthTokenCookieStr(token), 'header': self._SanitazeMunkiHeader(munki_header), } self.response.out.write(_EncodeMsg(d))
def get(self): """Handle GET.""" try: # already munki authenticated? return, nothing to do. gaeserver.DoMunkiAuth() #logging.info('Uauth: session is already authenticated') return except gaeserver.NotAuthenticated: pass user = users.get_current_user() if not user: #logging.error('Uauth: user is not logged in') raise NotAuthenticated email = user.email() if auth.IsAdminUser(email): a = gaeserver.AuthSimianServer() output = a.SessionCreateUserAuthToken(email, level=gaeserver.LEVEL_ADMIN) elif auth.IsSupportUser(email): a = gaeserver.AuthSimianServer() output = a.SessionCreateUserAuthToken(email, level=gaeserver.LEVEL_BASE) else: logging.error('Uauth: user %s is not an admin', email) raise NotAuthenticated if output: #logging.info('Uauth: success, token = %s', output) self.response.headers['Set-Cookie'] = '%s=%s; secure; httponly;' % ( auth_init.AUTH_TOKEN_COOKIE, output) self.response.out.write(auth_init.AUTH_TOKEN_COOKIE) else: #logging.info('Uauth: unknown token') raise NotAuthenticated
def put(self, filename): """PUT Args: filename: string like Firefox-1.0.dmg """ session = gaeserver.DoMunkiAuth( require_level=gaeserver.LEVEL_UPLOADPKG) filename = urllib.unquote(filename) hash_str = self.request.get('hash') catalogs = self.request.get('catalogs', None) manifests = self.request.get('manifests', None) install_types = self.request.get('install_types') if catalogs == '': catalogs = [] elif catalogs: catalogs = catalogs.split(',') if manifests == '': manifests = [] elif manifests: manifests = manifests.split(',') if install_types: install_types = install_types.split(',') mpl = MunkiPackageInfoPlistStrict(self.request.body) try: mpl.Parse() except plist.PlistError, e: logging.exception('Invalid pkginfo plist PUT: \n%s\n', self.request.body) self.response.set_status(httplib.BAD_REQUEST) self.response.out.write(str(e)) return
def post(self): """POST Parameters: filename: filename of package e.g. 'Firefox-1.0.dmg' """ session = gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG) filename = self.request.get('filename') pkginfo = models.PackageInfo.get_by_key_name(filename) if not pkginfo: self.response.set_status(404) self.response.out.write('Pkginfo does not exist: %s' % filename) return plist = pkginfo.plist catalogs = pkginfo.catalogs install_types = pkginfo.install_types #logging.info('Deleting package: %s', filename) blobstore_key = pkginfo.blobstore_key # Delete the PackageInfo entity, and then the package Blobstore entity. pkginfo.delete() gae_util.SafeBlobDel(blobstore_key) # Recreate catalogs so references to this package don't exist anywhere. for catalog in catalogs: models.Catalog.Generate(catalog) # Log admin delete to Datastore. user = session.uuid admin_log = models.AdminPackageLog( user=user, action='deletepkg', filename=filename, catalogs=catalogs, install_types=install_types, plist=plist) admin_log.put()
def put(self, file_type=None, file_name=None): """UploadFile PUT handler. Returns: A webapp.Response() response. """ session = gaeserver.DoMunkiAuth() uuid = main_common.SanitizeUUID(session.uuid) if not file_type or not file_name: logging.warning('file_type=%s , file_name=%s', file_type, file_name) self.error(httplib.NOT_FOUND) return if file_type == 'log': key = '%s_%s' % (uuid, file_name) l = models.ClientLogFile(key_name=key) l.log_file = self.request.body l.uuid = uuid l.name = file_name try: l.put() except (apiproxy_errors.RequestTooLargeError, datastore_errors.BadRequestError): # detastore raises BadRequestError now, if file too large. logging.warning( 'UploadFile may be log too large; truncating...') # Datastore has a 1MB entity limit and models.ClientLogFile.log_file # uses zlib compression. Anecdotal evidence of a handlful of log files # over 8MB in size compress down to well under 1MB. Therefore, slice # the top of the log data off at a conversative max, before retrying the # Datastore put. max_log_size_bytes = 5 * 1024 * 1024 l.log_file = ( '*** Log truncated by Simian due to size ***\n\n' + self.request.body[-1 * max_log_size_bytes:]) l.put() c = models.Computer.get_by_key_name(uuid) recipients = c.upload_logs_and_notify c.upload_logs_and_notify = None c.put() # c.upload_logs_and_notify may be None from a previous upload, as multiple # files may be uploaded in different requests per execution. if recipients: recipients = recipients.split(',') deferred.defer(SendNotificationEmail, recipients, c, settings.SERVER_HOSTNAME) else: self.error(httplib.NOT_FOUND)
def get(self, msg='', unused_provided_by_softwareupdate=''): """AppleSUS get handler. Args: name: str, catalog name to get. unused_provided_by_softwareupdate: Apple softwareupdate appends filename to our url. """ if msg: # Clients first POST to this handler and receive a URL with an embedded, # encrypted cookie-set containing an Auth1Token. When GET is called on # this URL, we must decode and unpack the cookie/Auth1Token, and set # the HTTP_COOKIE environment variable for DoMunkiAuth to validate. try: d = _DecodeMsg(msg) except ValueError: self.response.set_status(httplib.BAD_REQUEST) return os.environ['HTTP_COOKIE'] = str(d['cookies']) self.request.headers[MUNKI_CLIENT_ID_HEADER_KEY] = d['header'] session = gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_APPLESUS) client_id = handlers.GetClientIdForRequest(self.request, session=session) # get only major.minor os_version, stripping miniscule versioning. # i.e. 10.6.6 becomes 10.6, 10.23.6.x.x becomes 10.23 full_os_version = client_id.get('os_version', '') os_version = '.'.join(full_os_version.split('.', 2)[:2]) track = client_id.get('track', 'stable') catalog_name = '%s_%s' % (os_version, track) catalog = models.AppleSUSCatalog.MemcacheWrappedGet(catalog_name) if not catalog: logging.warning('Apple SUS catalog not found: %s', catalog_name) self.response.set_status(httplib.NOT_FOUND) return header_date_str = self.request.headers.get('If-Modified-Since', '') catalog_date = catalog.mtime if handlers.IsClientResourceExpired(catalog_date, header_date_str): self.response.headers['Last-Modified'] = catalog_date.strftime( handlers.HEADER_DATE_FORMAT) self.response.headers['Content-Type'] = 'text/xml; charset=utf-8' self.response.out.write(catalog.plist) else: self.response.set_status(httplib.NOT_MODIFIED)
def put(self, name): """AppleSUS put handler. Args: name: str, catalog name to put. """ gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG) # try loading for validation's sake c = plist.AppleSoftwareCatalogPlist(self.request.body) try: c.Parse() except plist.PlistError, e: logging.exception('Invalid Apple SUS catalog format: %s', str(e)) self.response.set_status(400) self.response.out.write(str(e)) return
def post(self): """POST method. This method behaves a little strangely. BlobstoreUploadHandler only allows returns statuses of 301, 302, 303 (not even 200), so one must redirect away to return more information to the caller. Parameters: file: package file contents pkginfo: packageinfo file contents name: filename of package e.g. 'Firefox-1.0.dmg' """ # Only blobstore/upload service/scotty requests should be # invoking this handler. if not handlers.IsBlobstore(): logging.critical('POST to /uploadpkg not from Blobstore: %s', self.request.headers) self.redirect('/') gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG) user = self.request.get('user') filename = self.request.get('name') install_types = self.request.get('install_types') catalogs = self.request.get('catalogs', None) manifests = self.request.get('manifests', None) if catalogs is None or not install_types or not user or not filename: msg = 'uploadpkg POST required parameters missing' logging.error(msg) self.redirect('/uploadpkg?mode=error&msg=%s' % msg) return if catalogs == '': catalogs = [] else: catalogs = catalogs.split(',') if manifests in ['', None]: manifests = [] else: manifests = manifests.split(',') install_types = install_types.split(',') upload_files = self.get_uploads('file') upload_pkginfo_files = self.get_uploads('pkginfo') if not len(upload_pkginfo_files) and not self.request.get('pkginfo'): self.redirect('/uploadpkg?mode=error&msg=No%20file%20received') return if len(upload_pkginfo_files): # obtain the pkginfo from a blob, and then throw it away. this is # a necessary hack because the upload handler grabbed it, but we don't # intend to keep it in blobstore. pkginfo_str = gae_util.GetBlobAndDel(upload_pkginfo_files[0].key()) else: # otherwise, grab the form parameter. pkginfo_str = self.request.get('pkginfo') blob_info = upload_files[0] blobstore_key = str(blob_info.key()) # Parse, validate, and encode the pkginfo plist. plist = plist_lib.MunkiPackageInfoPlist(pkginfo_str) try: plist.Parse() except plist_lib.PlistError: logging.exception('Invalid pkginfo plist uploaded:\n%s\n', pkginfo_str) gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=No%20valid%20pkginfo%20received') return filename = plist['installer_item_location'] pkgdata_sha256 = plist['installer_item_hash'] # verify the blob was actually written; in case Blobstore failed to write # the blob but still POSTed to this handler (very, very rare). blob_info = blobstore.BlobInfo.get(blobstore_key) if not blob_info: logging.critical( 'Blobstore returned a key for %s that does not exist: %s', filename, blobstore_key) self.redirect('/uploadpkg?mode=error&msg=Blobstore%20failure') return # Obtain a lock on the PackageInfo entity for this package. lock = 'pkgsinfo_%s' % filename if not gae_util.ObtainLock(lock, timeout=5.0): gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=Could%20not%20lock%20pkgsinfo') return old_blobstore_key = None pkg = models.PackageInfo.get_or_insert(filename) if not pkg.IsSafeToModify(): gae_util.ReleaseLock(lock) gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=Package%20is%20not%20modifiable') return if pkg.blobstore_key: # a previous blob exists. delete it when the update has succeeded. old_blobstore_key = pkg.blobstore_key pkg.blobstore_key = blobstore_key pkg.name = plist.GetPackageName() pkg.filename = filename pkg.user = user pkg.catalogs = catalogs pkg.manifests = manifests pkg.install_types = install_types pkg.plist = plist pkg.pkgdata_sha256 = pkgdata_sha256 # update the PackageInfo model with the new plist string and blobstore key. try: pkg.put() success = True except db.Error: logging.exception('error on PackageInfo.put()') success = False # if it failed, delete the blob that was just uploaded -- it's # an orphan. if not success: gae_util.SafeBlobDel(blobstore_key) # if this is a new entity (get_or_insert puts), attempt to delete it. if not old_blobstore_key: gae_util.SafeEntityDel(pkg) gae_util.ReleaseLock(lock) self.redirect('/uploadpkg?mode=error') return # if an old blob was associated with this Package, delete it. # the new blob that was just uploaded has replaced it. if old_blobstore_key: gae_util.SafeBlobDel(old_blobstore_key) gae_util.ReleaseLock(lock) # Generate catalogs for newly uploaded pkginfo plist. for catalog in pkg.catalogs: models.Catalog.Generate(catalog, delay=1) # Log admin upload to Datastore. admin_log = models.AdminPackageLog(user=user, action='uploadpkg', filename=filename, catalogs=catalogs, manifests=manifests, install_types=install_types, plist=pkg.plist.GetXml()) admin_log.put() self.redirect('/uploadpkg?mode=success&key=%s' % blobstore_key)
def post(self): """Reports get handler. Returns: A webapp.Response() response. """ session = gaeserver.DoMunkiAuth() uuid = main_common.SanitizeUUID(session.uuid) report_type = self.request.get('_report_type') feedback_requested = self.request.get('_feedback') message = None details = None client_id = None computer = None if report_type == 'preflight' or report_type == 'postflight': client_id_str = urllib.unquote(self.request.get('client_id')) client_id = common.ParseClientId(client_id_str, uuid=uuid) user_settings_str = self.request.get('user_settings') user_settings = None try: if user_settings_str: user_settings = util.Deserialize( urllib.unquote(str(user_settings_str))) except util.DeserializeError: logging.warning('Client %s sent broken user_settings: %s', client_id_str, user_settings_str) pkgs_to_install = self.request.get_all('pkgs_to_install') apple_updates_to_install = self.request.get_all( 'apple_updates_to_install') computer = models.Computer.get_by_key_name(uuid) ip_address = os.environ.get('REMOTE_ADDR', '') report_feedback = None if report_type == 'preflight': # if the UUID is known to be lost/stolen, log this connection. if models.ComputerLostStolen.IsLostStolen(uuid): logging.warning('Connection from lost/stolen machine: %s', uuid) models.ComputerLostStolen.LogLostStolenConnection( computer=computer, ip_address=ip_address) # we want to get feedback now, before preflight_datetime changes. if feedback_requested: client_exit = self.request.get('client_exit', None) report_feedback = self.GetReportFeedback( uuid, report_type, computer=computer, ip_address=ip_address, client_exit=client_exit) self.response.out.write(report_feedback) # if report feedback calls for a client exit, log it. if report_feedback == common.ReportFeedback.EXIT: if not client_exit: # client didn't ask for an exit, which means server decided. client_exit = 'Connection from defined exit IP address' common.WriteClientLog(models.PreflightExitLog, uuid, computer=computer, exit_reason=client_exit) common.LogClientConnection(report_type, client_id, user_settings, pkgs_to_install, apple_updates_to_install, computer=computer, ip_address=ip_address, report_feedback=report_feedback) elif report_type == 'install_report': computer = models.Computer.get_by_key_name(uuid) self._LogInstalls(self.request.get_all('installs'), computer) for removal in self.request.get_all('removals'): common.WriteClientLog(models.ClientLog, uuid, computer=computer, action='removal', details=removal) for problem in self.request.get_all('problem_installs'): common.WriteClientLog(models.ClientLog, uuid, computer=computer, action='install_problem', details=problem) elif report_type == 'preflight_exit': # NOTE(user): only remains for older clients. message = self.request.get('message') computer = common.WriteClientLog(models.PreflightExitLog, uuid, exit_reason=message) elif report_type == 'broken_client': # Default reason of "objc" to support legacy clients, existing when objc # was the only broken state ever reported. reason = self.request.get('reason', 'objc') details = self.request.get('details') logging.warning('Broken Munki client (%s): %s', reason, details) common.WriteBrokenClient(uuid, reason, details) elif report_type == 'msu_log': details = {} for k in ['time', 'user', 'source', 'event', 'desc']: details[k] = self.request.get(k, None) common.WriteComputerMSULog(uuid, details) else: # unknown report type; log all post params. params = [] for param in self.request.arguments(): params.append('%s=%s' % (param, self.request.get_all(param))) common.WriteClientLog(models.ClientLog, uuid, action='unknown', details=str(params)) # If the client asked for feedback, get feedback and respond. # Skip this if the report_type is preflight, as report feedback was # retrieved before LogComputerConnection changed preflight_datetime. if feedback_requested and report_type != 'preflight': self.response.out.write( self.GetReportFeedback( uuid, report_type, message=message, details=details, computer=computer, ))
def post(self): """Reports get handler. Returns: A webapp.Response() response. """ session = gaeserver.DoMunkiAuth() uuid = main_common.SanitizeUUID(session.uuid) report_type = self.request.get('_report_type') report_feedback = {} message = None details = None client_id = None computer = None if report_type == 'preflight' or report_type == 'postflight': client_id_str = urllib.unquote(self.request.get('client_id')) client_id = common.ParseClientId(client_id_str, uuid=uuid) user_settings_str = self.request.get('user_settings') user_settings = None try: if user_settings_str: user_settings = util.Deserialize( urllib.unquote(str(user_settings_str))) except util.DeserializeError: logging.warning('Client %s sent broken user_settings: %s', client_id_str, user_settings_str) pkgs_to_install = self.request.get_all('pkgs_to_install') apple_updates_to_install = self.request.get_all( 'apple_updates_to_install') computer = models.Computer.get_by_key_name(uuid) ip_address = os.environ.get('REMOTE_ADDR', '') if report_type == 'preflight': # we want to get feedback now, before preflight_datetime changes. client_exit = self.request.get('client_exit', None) report_feedback = self.GetReportFeedback( uuid, report_type, computer=computer, ip_address=ip_address, client_exit=client_exit) if self.request.get('json') == '1': self.response.out.write(JSON_PREFIX + json.dumps(report_feedback)) else: # For legacy clients that accept a single string, not JSON. feedback_to_send = 'OK' for feedback in LEGACY_FEEDBACK_LIST: if report_feedback.get(feedback.lower()): feedback_to_send = feedback self.response.out.write(feedback_to_send) # if report feedback calls for a client exit, log it. if report_feedback.get('exit'): if not client_exit: # client didn't ask for an exit, which means server decided. client_exit = 'Connection from defined exit IP address' common.WriteClientLog(models.PreflightExitLog, uuid, computer=computer, exit_reason=client_exit) common.LogClientConnection(report_type, client_id, user_settings, pkgs_to_install, apple_updates_to_install, computer=computer, ip_address=ip_address, report_feedback=report_feedback) elif report_type == 'install_report': computer = models.Computer.get_by_key_name(uuid) self._LogInstalls(self.request.get_all('installs'), computer) for removal in self.request.get_all('removals'): common.WriteClientLog(models.ClientLog, uuid, computer=computer, action='removal', details=removal) for problem in self.request.get_all('problem_installs'): common.WriteClientLog(models.ClientLog, uuid, computer=computer, action='install_problem', details=problem) elif report_type == 'broken_client': # Default reason of "objc" to support legacy clients, existing when objc # was the only broken state ever reported. reason = self.request.get('reason', 'objc') details = self.request.get('details') logging.warning('Broken Munki client (%s): %s', reason, details) common.WriteBrokenClient(uuid, reason, details) elif report_type == 'msu_log': details = {} for k in ['time', 'user', 'source', 'event', 'desc']: details[k] = self.request.get(k, None) common.WriteComputerMSULog(uuid, details) else: # unknown report type; log all post params. params = [] for param in self.request.arguments(): params.append('%s=%s' % (param, self.request.get_all(param))) common.WriteClientLog(models.ClientLog, uuid, action='unknown', details=str(params))
def testDoMunkiAuth(self): """Test DoMunkiAuth().""" level = 123 cookie_str = 'foo=bar' token = 'cookie value for auth.AUTH_TOKEN_COOKIE' uuid = 'session uuid' mock_valobj = self.mox.CreateMockAnything() mock_valobj.value = token mock_session = self.mox.CreateMockAnything() mock_session.uuid = 'session uuid' mock_environ = self.mox.CreateMockAnything() mock_cookie = self.mox.CreateMockAnything() mock_auth1 = self.mox.CreateMockAnything() self.stubs.Set(gaeserver.os, 'environ', mock_environ) self.mox.StubOutWithMock(gaeserver.Cookie, 'SimpleCookie', True) self.mox.StubOutWithMock(gaeserver, 'AuthSimianServer', True) # 0: fake_noauth=True, nothing to mock # test 1: missing cookie mock_environ.get('HTTP_COOKIE', None).AndReturn(None) # test 2: cookie is malformed mock_environ.get('HTTP_COOKIE', None).AndReturn(cookie_str) gaeserver.Cookie.SimpleCookie().AndReturn(mock_cookie) mock_cookie.load(cookie_str).AndRaise(TypeError) # test 3: cookie exists, but isn't ours mock_environ.get('HTTP_COOKIE', None).AndReturn(cookie_str) gaeserver.Cookie.SimpleCookie().AndReturn(mock_cookie) mock_cookie.load(cookie_str).AndRaise(gaeserver.Cookie.CookieError) # test 4: cookie exists, is ours, but token isn't authenticated mock_environ.get('HTTP_COOKIE', None).AndReturn(cookie_str) gaeserver.Cookie.SimpleCookie().AndReturn(mock_cookie) mock_cookie.load(cookie_str).AndReturn(None) mock_cookie.__contains__( gaeserver.auth.AUTH_TOKEN_COOKIE).AndReturn(False) # test 5: GetSessionIfAuthOK() returns false, bad token mock_environ.get('HTTP_COOKIE', None).AndReturn(cookie_str) gaeserver.Cookie.SimpleCookie().AndReturn(mock_cookie) mock_cookie.load(cookie_str).AndReturn(None) mock_cookie.__contains__( gaeserver.auth.AUTH_TOKEN_COOKIE).AndReturn(True) mock_cookie.__getitem__( gaeserver.auth.AUTH_TOKEN_COOKIE).AndReturn(mock_valobj) gaeserver.AuthSimianServer().AndReturn(mock_auth1) mock_cookie.__getitem__( gaeserver.auth.AUTH_TOKEN_COOKIE).AndReturn(mock_valobj) mock_auth1.GetSessionIfAuthOK(token, gaeserver.LEVEL_BASE).AndRaise( gaeserver.base.AuthSessionError) # 6: test all success! mock_environ.get('HTTP_COOKIE', None).AndReturn(cookie_str) gaeserver.Cookie.SimpleCookie().AndReturn(mock_cookie) mock_cookie.load(cookie_str).AndReturn(None) mock_cookie.__contains__( gaeserver.auth.AUTH_TOKEN_COOKIE).AndReturn(True) mock_cookie.__getitem__( gaeserver.auth.AUTH_TOKEN_COOKIE).AndReturn(mock_valobj) gaeserver.AuthSimianServer().AndReturn(mock_auth1) mock_cookie.__getitem__( gaeserver.auth.AUTH_TOKEN_COOKIE).AndReturn(mock_valobj) mock_auth1.GetSessionIfAuthOK(token, level).AndReturn(mock_session) self.mox.ReplayAll() self.assertRaises( gaeserver.NotAuthenticated, gaeserver.DoMunkiAuth, fake_noauth=True) # 0 self.assertRaises(gaeserver.NotAuthenticated, gaeserver.DoMunkiAuth) # 1 self.assertRaises(gaeserver.NotAuthenticated, gaeserver.DoMunkiAuth) # 2 self.assertRaises(gaeserver.NotAuthenticated, gaeserver.DoMunkiAuth) # 3 self.assertRaises(gaeserver.NotAuthenticated, gaeserver.DoMunkiAuth) # 4 self.assertRaises(gaeserver.NotAuthenticated, gaeserver.DoMunkiAuth) # 5 session = gaeserver.DoMunkiAuth(require_level=level) # 6 self.assertEqual(uuid, session.uuid) # 6 self.mox.VerifyAll()
def post(self): """POST This method behaves a little strangely. BlobstoreUploadHandler only allows returns statuses of 301, 302, 303 (not even 200), so one must redirect away to return more information to the caller. Parameters: file: package file contents pkginfo: packageinfo file contents name: filename of package e.g. 'Firefox-1.0.dmg' """ # Only blobstore/upload service/scotty requests should be # invoking this handler. if not handlers.IsBlobstore(): logging.critical('POST to /uploadpkg not from Blobstore: %s', self.request.headers) self.redirect('/') gaeserver.DoMunkiAuth(require_level=gaeserver.LEVEL_UPLOADPKG) user = self.request.get('user') filename = self.request.get('name') install_types = self.request.get('install_types') catalogs = self.request.get('catalogs', None) manifests = self.request.get('manifests', None) if catalogs is None or not install_types or not user or not filename: msg = 'uploadpkg POST required parameters missing' logging.error(msg) self.redirect('/uploadpkg?mode=error&msg=%s' % msg) return if catalogs == '': catalogs = [] else: catalogs = catalogs.split(',') if manifests in ['', None]: manifests = [] else: manifests = manifests.split(',') install_types = install_types.split(',') upload_files = self.get_uploads('file') upload_pkginfo_files = self.get_uploads('pkginfo') if not len(upload_pkginfo_files) and not self.request.get('pkginfo'): self.redirect('/uploadpkg?mode=error&msg=No%20file%20received') return if len(upload_pkginfo_files): # obtain the pkginfo from a blob, and then throw it away. this is # a necessary hack because the upload handler grabbed it, but we don't # intend to keep it in blobstore. pkginfo_str = gae_util.GetBlobAndDel(upload_pkginfo_files[0].key()) else: # otherwise, grab the form parameter. pkginfo_str = self.request.get('pkginfo') blob_info = upload_files[0] blobstore_key = str(blob_info.key()) # Parse, validate, and encode the pkginfo plist. plist = plist_lib.MunkiPackageInfoPlist(pkginfo_str) try: plist.Parse() except plist_lib.PlistError, e: logging.exception('Invalid pkginfo plist uploaded:\n%s\n', pkginfo_str) gae_util.SafeBlobDel(blobstore_key) self.redirect( '/uploadpkg?mode=error&msg=No%20valid%20pkginfo%20received') return