Ejemplo n.º 1
0
def process( app ):
	query = app.query
	response = app.response
	session = app.session
	source_id = int( query.parms["id"] )
	mode = query.parms["mode"] if "mode" in query.parms else "convert"
	if app.user.can_read( source_id ):
		source_obj = files.File( app, object_id=source_id )
		if re.match( r"^video/.*", source_obj.media_type ):
			new_poster_offset = float(query.parms["poster_offset"]) if "poster_offset" in query.parms else None
			new_poster_id = int(query.parms["poster_id"]) if "poster_id" in query.parms else None
			if new_poster_id and app.user.can_write( source_id ) and app.user.can_read( new_poster_id ):
				new_poster_obj = files.File( app, object_id=new_poster_id )
			else:
				new_poster_obj = None
			# zur Bestimmung der Größen und Bitraten der Alternativobjekte identifizieren wir zunächst das Orignalobjekt:
			source_size = source_obj.get_size()
			source_meta = source_obj.identify()
			source_width = int( source_meta["mplayer"]["id"]["video"]["width"] )
			source_rate = round(source_size*8/float(source_meta["mplayer"]["id"]["length"])/1000)
			results = []
			class ConversionDescription:
				def __init__( self, role, width, media_type, rate=0, condition=lambda x: True ):
					self.role = role
					self.width = width
					self.media_type = media_type
					self.rate = rate
					self.condition = condition
				def applies( self ):
					return self.condition(self)
				def __eq__( self, other ):
					# rate is not part of equality, because this is used to compare conversion candidates with already existing substitutes 
					# and bitrate is deemed to specific for that purpose; we just assume rates are ok/equal for a given size
					return (	(self.role, self.width, self.media_type, self.applies()) ==
								(other.role, other.width, other.media_type, other.applies()) )
				def __str__( self ):
					return( "(role=%s, width=%dpx, media_type=%s, rate=%dk, applies=%s)" % (self.role, self.width, self.media_type, self.rate, str(self.applies())) )
			# we want a poster image, an mp4-substitute in source_width for non-mp4 sources and a scaled down mp4-substitute for big sources:
			missing_conversions = [
				ConversionDescription( role="poster", width=min(1280, source_width), media_type="image/jpeg" ),
				ConversionDescription( role="compatible", width=source_width, media_type="video/mp4", rate=source_rate,
						  condition = lambda self:  source_obj.media_type != "video/mp4" ),
				ConversionDescription( role="compatible", width=min(1280, source_width), media_type="video/mp4", rate=min(2000, source_rate),
						  condition = lambda self: (self.width < (source_width*0.8)) and (self.rate < (source_rate*0.8)) ),
			]
			c = app.db.cursor()
			# Hier müssen wir zunächst prüfen ob das angefragte Objekt selbst schon ein Substitute-Objekt ist, ...
			c.execute( """select original_id from substitutes where substitute_id=?""", [source_obj.id] )
			if c.fetchone():
				# ... denn für Substitute-Objekte sollten keine weiteren Substitute-Objekte generiert werden.
				missing_conversions = []
			c.execute( """select s.substitute_id, s.type, s.size, s.priority, sobj.id from substitutes s
							left join objects sobj on sobj.id=s.substitute_id
							where s.original_id=?""", [source_obj.id] )
			for row in c:
				substitute = { "substitute_id" : int(row[0]), "type" : row[1], "size" : int(row[2]), "priority" : int(row[3]) }
				sobj_id = row[4]
				if sobj_id==None:
					# Zombie-Substitutes bereinigen (FIXME: sollte das von DBObject.delete() erledigt werden?):
					del_c = app.db.cursor()
					del_c.execute( """delete from substitutes where substitute_id=?""", [substitute["substitute_id"]] )
				else:
					substitute_obj = db_object.DBObject( app, object_id=substitute["substitute_id"] )
					conversion = ConversionDescription( role=substitute["type"], width=substitute["size"], media_type=substitute_obj.media_type )
					if conversion in missing_conversions:
						if substitute["type"]=="poster" and (new_poster_offset or new_poster_obj):
							# bestehendes Poster-Substitute entfernen, da es neu definiert werden soll:
							del_c = app.db.cursor()
							del_c.execute( """delete from substitutes where original_id=? and substitute_id=?""", [source_obj.id,substitute_obj.id] )
						else:
							missing_conversions.remove( conversion )
							results.append( substitute )
					else:
						results.append( substitute )
			error_list = []
			if mode == "convert":
				# Alle fehlende Objekte sofort ohne Daten anlegen, um Mehrfachkonvertierung zu vermeiden:
				new_objects = []
				for conversion in [x for x in missing_conversions if x.applies()]:
					# Privilege-Escalation damit nicht nur der Eigentümer des Target-Objekts diesen Code ausführen kann:
					app_old_user = app.user
					app.user = user.get_admin_user(app)
					existing_object_id = None
					if conversion.role=="poster" and new_poster_obj:
						existing_object_id = new_poster_obj.id
					new_obj = files.File( app, object_id=existing_object_id, parent_id=source_obj.id, media_type=conversion.media_type )
					if not existing_object_id:
						new_obj.conversion = conversion;
						new_objects.append( new_obj )
					substitute = { "substitute_id" : new_obj.id, "type" : conversion.role, "size" : conversion.width, "priority" : None }
					results.append( substitute )
					app.user = app_old_user
					c = app.db.cursor()
					c.execute( """insert into substitutes (original_id, substitute_id, type, size) values(?,?,?,?)""", 
														[source_obj.id, new_obj.id, conversion.role, conversion.width] )
				# Konvertierungsvorgänge für angelegte Objekte durchführen:
				for new_obj in new_objects:
					# this may take a long time, so wie have to commit first:
					app.db.commit()
					base_type, sub_type = new_obj.media_type.split("/")
					new_tmp_name = new_obj.storage_path+".tmp."+sub_type
					if( re.match(r"^video/.*", new_obj.media_type) ):
						# Konvertierung mit geänderter Breite bei Erhaltug des Seitenverhältnisses:
						# http://stackoverflow.com/questions/8218363/maintaining-ffmpeg-aspect-ratio
						p = subprocess.Popen( ["ffmpeg", "-y", "-i", source_obj.storage_path, "-vf", "scale=%d:trunc(ow/a/2)*2" % (new_obj.conversion.width), 
													"-r", "25", "-b", "%dk" % new_obj.conversion.rate, "-qmin", "0", "-strict", "-2", new_tmp_name],
												stdout=subprocess.PIPE, stderr=subprocess.PIPE )
					elif( new_obj.conversion.role=="poster" and new_obj.media_type == "image/jpeg" ):
						# Vorschaubild bei Zeitindex 3s extrahieren:
						p = subprocess.Popen( ["ffmpeg", "-y", "-i", source_obj.storage_path, "-vf", "scale=%d:trunc(ow/a/2)*2" % (new_obj.conversion.width), 
													"-ss", str(new_poster_offset if new_poster_offset else 3), "-vframes", "1", new_tmp_name],
												stdout=subprocess.PIPE, stderr=subprocess.PIPE )
					else:
						raise NotImplementedError( "missing operation for conversion: %s" % (str(new_obj.conversion)) )
					stdout, stderr = p.communicate()
					if p.returncode!=0:
						try:
							# FIXME: Löschenfunktion nach DBObject ausmodularisieren und Dateibereinigung nach files.File:
							# Privilege-Escalation damit nicht nur der Eigentümer des Target-Objekts diesen Code ausführen kann:
							app_old_user = app.user
							app.user = user.get_admin_user(app)
							db_object.DBObject.delete_in( app, [new_obj.id] )
							app.user = app_old_user
							os.remove( new_tmp_name )
							c = app.db.cursor()
							c.execute( """delete from substitutes where original_id=? and substitute_id=?""", [source_obj.id, new_obj.id] )
							results = [x for x in results if x["substitute_id"]!=new_obj.id]
						except Exception as e:
							error_list.append( e )
						errmsg = stderr.decode().split("\n")[-1]
						error_list.append( errors.InternalProgramError(errmsg) )
					else:
						os.rename( new_tmp_name, new_obj.storage_path )
			# Fehlerbehandlung:
			if error_list:
				msg = ""
				for error in error_list:
					if msg:
						msg += "; "
					msg += str(error)
				raise errors.InternalProgramError( msg )
			else:
				for result in results:
					result["substitute_object"] = get_module.get(app, object_ids=[result["substitute_id"]])
				response.output = json.dumps( {"succeeded": True,
										"substitutes": results} )
		elif re.match( r"^audio/.*", source_obj.media_type ):
			# TODO: add safe conversions
			results = []
			response.output = json.dumps( {"succeeded": True,
									"substitutes": results} )
		else:
			raise NotImplementedError( "unsupported media type: "+source_obj.media_type )
	else:
		raise errors.PrivilegeError()
Ejemplo n.º 2
0
def process( app ):
	"""Registrierungsanfrage prüfen (Eindeutigkeit des Nutzernamens, syntaktische 
		Korrektheit der Email-Adresse, korrekte Doppelangabe und kryptographische 
		Mindestanforderung des Passwortes) und ggf. vorläufiges Nutzerobjekt anlegen
		und Bestätigungsanfrage im E-Mail-Ausgang speichern, die einen Link auf 
		dieses Modul mit einer zusätzlichen E-Mail-Session-ID (msid) 
		enthält. Die msid wurde zuvor, genau wie der eindeutige Schlüssel des
		vorläufigen Nutzerobjektes, serverseitig im Session-Parametersatz von sid 
		hinterlegt. Die Freischaltung der Registrierung sollte nur erfolgen, wenn 
		dieses Modul über den Bestätigungslink aufgerufen wurde (msid vorhanden) und 
		URL-msid sowie serverseitig hinterlegte Session-msid übereinstimmen. Falls 
		der msid-Vergleich fehlschlägt könnte dem Nutzer angeboten werden die 
		E-Mail-Verifizierung via Knopfdruck mit neuer msid zu wiederholen, da es
		sein kann, dass die ursprüngliche Registrierung versehentlich in einem 
		anderen Browser erfolgte, als der spätere Abruf des E-Mail-Links 
		(Standardbrowser). Hierzu müsste msid als reguläre Session-Kopie von sid
		angelegt worden und genau wie sid mit dem vorläufigen Nutzerobjekt
		verknüpft worden sein, um als gültige Fallback-Session fungieren zu können. 
		Alternativ könnnte man einfach sid und msid in den E-Mail-Link aufnehmen.
		Beide Fehlertoleranz-Ansätze sind verwundbar für Lauschangriffe auf den
		E-Mail-Kanal."""
	query = app.query
	response = app.response
	session = app.session
	if "msid" in query.parms:
		confirmation_session = application.Session( app, query.parms["msid"] )
		#if "registration_sid" in confirmation_session.parms \
		#and confirmation_session.parms["registration_sid"] == session.sid \
		if "registration_user_id" in confirmation_session.parms:
			user_id = int( confirmation_session.parms["registration_user_id"] )
			usr = user.User( app=app, user_id=user_id )
			# Nutzer Lese-/Schreib-Zugriff auf sein eigenes Nutzerobjekt geben:
			usr.grant_read( user_id )
			usr.grant_write( user_id )
			confirmation_session.parms=[]
			confirmation_session.store()
			response.output = json.dumps( {"succeeded" : True} )
			try:
				send_registration_notification( app=app, usr=usr )
			except Exception as e:
				for line in traceback.format_exception( Exception, e, e.__traceback__ ):
					app.log( line )
			return
		else:
			try:
				send_registration_failed_notification( app=app, confirmation_session=confirmation_session )
			except Exception as e:
				for line in traceback.format_exception( Exception, e, e.__traceback__ ):
					app.log( line )
			raise Exception( "Registration confirmation failed" )
	elif "nick" in query.parms and "password" in query.parms \
	and "email" in query.parms:
		nick = query.parms["nick"]
		password = query.parms["password"]
		email = query.parms["email"]
		if "reconfirm" in query.parms:
			# Bei gültigen Login-Daten (keine Ausnahme in check_login),
			# Neuauslösung der Email-Bestätigung an eine ggf. geänderte 
			# Adresse erlauben:
			usr = login.check_login( app )
			app_old_user = app.user
			app.user = user.get_admin_user(app)
			usr.update( email=email )
			app.user = app_old_user
			send_confirmation_request( app=app, user_id=usr.id )
			response.output = json.dumps( {"succeeded" : True} )
			return
		else:
			app_old_user = app.user
			app.user = user.get_admin_user(app)
			usr = user.User( app=app, parent_id=1, nick=nick, plain_password=password,
									email=email )
			app.user = app_old_user
			# FIXME: Falls das Versenden der E-Mail hier sofort fehlschlägt,
			# müssen wir user.create entweder zurückrollen oder den 
			# fehlgeschlagenen Zustellungsversuch zwischenspeichern, sodass
			# er vom Administrator oder einem Cronjob später nochmal ausgelöst
			# werden kann:
			send_confirmation_request( app=app, user_id=usr.id )
			response.output = json.dumps( {"succeeded" : True} )
			return
	else:
		raise Exception( "Missing parameters" )
Ejemplo n.º 3
0
def process( app ):
	query = app.query
	response = app.response
	session = app.session
	if "msid" in query.parms:
		confirmation_session = application.Session( app, query.parms["msid"] )
		if "password_recovery_sid" in confirmation_session.parms \
		and "password_recovery_user_id" in confirmation_session.parms:
			user_id = int( confirmation_session.parms["password_recovery_user_id"] )
			if "nick" in query.parms and "new_password" in query.parms \
			and confirmation_session.parms["password_recovery_sid"] == session.sid:
				# Asking the user requesting password recovery for his own nick might add a thin line
				# of additional security against the registered email account being controlled by an attacker,
				# as long as the nick is not disclosed in the recovery request confirmation mail.
				usr = user.User( app=app, user_id=user_id )
				usr_status = usr.status()
				if( usr_status["login"]["nick"]!=query.parms["nick"] ):
					try:
						send_password_recovery_failed_notification( app=app, confirmation_session=confirmation_session )
					except Exception as e:
						for line in traceback.format_exception( Exception, e, e.__traceback__ ):
							app.log( line )
					raise Exception( "Password recovery failed" )
				# We have to escalate to admin privileges for the password change procedure to not require old password validation
				app_old_user = app.user
				app.user = user.get_admin_user( app )
				usr.update( new_password=query.parms["new_password"] )
				app.user = app_old_user
				# Purge confirmation session
				confirmation_session.parms=[]
				confirmation_session.store()
				response.output = json.dumps( {"succeeded" : True} )
				try:
					send_password_recovery_notification( app=app, usr=usr )
				except Exception as e:
					for line in traceback.format_exception( Exception, e, e.__traceback__ ):
						app.log( line )
				return
			elif "cancel" in query.parms \
			and user_id == app.user.id:
				# Purge confirmation session
				confirmation_session.parms=[]
				confirmation_session.store()
				response.output = json.dumps( {"succeeded" : True} )
				try:
					send_password_recovery_cancel_notification( app=app, usr=usr )
				except Exception as e:
					for line in traceback.format_exception( Exception, e, e.__traceback__ ):
						app.log( line )
				return
			else:
				try:
					send_password_recovery_failed_notification( app=app, confirmation_session=confirmation_session )
				except Exception as e:
					for line in traceback.format_exception( Exception, e, e.__traceback__ ):
						app.log( line )
				raise Exception( "Password recovery failed" )
		else:
			try:
				send_password_recovery_failed_notification( app=app, confirmation_session=confirmation_session )
			except Exception as e:
				for line in traceback.format_exception( Exception, e, e.__traceback__ ):
					app.log( line )
			raise Exception( "Password recovery failed" )
	elif "nick" in query.parms and "email" in query.parms:
		nick = query.parms["nick"]
		email = query.parms["email"]
		c = app.db.cursor()
		c.execute(
			"""select object_id from users where nick=? and email=?""",
			[nick, email] )
		result = c.fetchone()
		if result==None:
			raise Exception( "Invalid user name or email address" )
		else:
			user_id = int( result[0] )
			app_old_user = app.user
			app.user = user.get_admin_user( app )
			usr = user.User( app, user_id=user_id )
			app.user = app_old_user
			send_confirmation_request( app=app, user_id=usr.id )
			response.output = json.dumps( {"succeeded" : True} )
	else:
		raise Exception( "Missing parameters" )
Ejemplo n.º 4
0
def process(app):
    query = app.query
    response = app.response
    session = app.session
    target_id = int(query.parms["id"])
    mode = query.parms["mode"] if "mode" in query.parms else "convert"
    if app.user.can_read(target_id):
        target_obj = files.File(app, object_id=target_id)
        if re.match(r"^video/.*", target_obj.media_type):
            new_poster_offset = float(query.parms["poster_offset"]) if "poster_offset" in query.parms else None
            new_poster_id = int(query.parms["poster_id"]) if "poster_id" in query.parms else None
            if new_poster_id and app.user.can_write(target_id) and app.user.can_read(new_poster_id):
                new_poster_obj = files.File(app, object_id=new_poster_id)
            else:
                new_poster_obj = None
                # Wir brauchen min. webm (Firefox, Chrome) und mp4 (Safari, IE, Chrome) für eine halbwegs gute
                # Client-Abdeckung.
                # Direkte Kindobjekte danach durchsuchen:
            results = []
            missing_conversions = [
                ("poster", 480, "image/jpeg"),
                ("compatible", 480, "video/mp4"),
                ("compatible", 480, "video/webm"),
            ]
            c = app.db.cursor()
            # Hier müssen wir zunächst prüfen ob das angefragte Objekt selbst schon ein Substitute-Objekt ist, ...
            c.execute("""select original_id from substitutes where substitute_id=?""", [target_obj.id])
            if c.fetchone():
                # ... denn für Substitute-Objekte sollten keine weiteren Substitute-Objekte generiert werden.
                missing_conversions = []
            c.execute(
                """select s.substitute_id, s.type, s.size, s.priority, sobj.id from substitutes s
							left join objects sobj on sobj.id=s.substitute_id
							where s.original_id=?""",
                [target_obj.id],
            )
            for row in c:
                substitute = {
                    "substitute_id": int(row[0]),
                    "type": row[1],
                    "size": int(row[2]),
                    "priority": int(row[3]),
                }
                sobj_id = row[4]
                if sobj_id == None:
                    # Zombie-Substitutes bereinigen (FIXME: sollte das von DBObject.delete() erledigt werden?):
                    del_c = app.db.cursor()
                    del_c.execute("""delete from substitutes where substitute_id=?""", [substitute["substitute_id"]])
                    app.db.commit()
                else:
                    substitute_obj = db_object.DBObject(app, object_id=substitute["substitute_id"])
                    conversion = (substitute["type"], substitute["size"], substitute_obj.media_type)
                    if conversion in missing_conversions:
                        if substitute["type"] == "poster" and (new_poster_offset or new_poster_obj):
                            # bestehendes Poster-Substitute entfernen, da es neu definieter werden soll:
                            del_c = app.db.cursor()
                            del_c.execute(
                                """delete from substitutes where original_id=? and substitute_id=?""",
                                [target_obj.id, substitute_obj.id],
                            )
                            app.db.commit()
                        else:
                            missing_conversions.remove(conversion)
                            results.append(substitute)
            error_list = []
            if mode == "convert":
                # Alle fehlende Objekte sofort ohne Daten anlegen, um Mehrfachkonvertierung zu vermeiden:
                new_objects = []
                for conversion in missing_conversions:
                    conversion_type, conversion_size, new_media_type = conversion
                    # Privilege-Escalation damit nicht nur der Eigentümer des Target-Objekts diesen Code ausführen kann:
                    app_old_user = app.user
                    app.user = user.get_admin_user(app)
                    existing_object_id = None
                    if conversion_type == "poster" and new_poster_obj:
                        existing_object_id = new_poster_obj.id
                    new_obj = files.File(
                        app, object_id=existing_object_id, parent_id=target_obj.id, media_type=new_media_type
                    )
                    if not existing_object_id:
                        new_obj.conversion = conversion
                        new_objects.append(new_obj)
                    substitute = {
                        "substitute_id": new_obj.id,
                        "type": conversion_type,
                        "size": conversion_size,
                        "priority": None,
                    }
                    results.append(substitute)
                    app.user = app_old_user
                    c = app.db.cursor()
                    c.execute(
                        """insert into substitutes (original_id, substitute_id, type, size) values(?,?,?,?)""",
                        [target_obj.id, new_obj.id, conversion_type, conversion_size],
                    )
                    app.db.commit()
                    # Konvertierungsvorgänge für angelegte Objekte durchführen:
                for new_obj in new_objects:
                    conversion_type, conversion_size, ignored = new_obj.conversion
                    base_type, sub_type = new_obj.media_type.split("/")
                    new_tmp_name = new_obj.storage_path + ".tmp." + sub_type
                    if re.match(r"^video/.*", new_obj.media_type):
                        # Konvertierung mit konservativer Breite von 480px bei Erhaltug des Seitenverhältnisses:
                        # http://stackoverflow.com/questions/8218363/maintaining-ffmpeg-aspect-ratio
                        p = subprocess.Popen(
                            [
                                "ffmpeg",
                                "-y",
                                "-i",
                                target_obj.storage_path,
                                "-vf",
                                "scale=%d:trunc(ow/a/2)*2" % (conversion_size),
                                "-r",
                                "25",
                                "-b",
                                "1000k",
                                "-qmin",
                                "0",
                                "-strict",
                                "-2",
                                new_tmp_name,
                            ],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                        )
                    elif conversion_type == "poster" and new_obj.media_type == "image/jpeg":
                        # Vorschaubild bei Zeitindex 3s extrahieren (TODO: Mit beliebigem Zeitindex einstellbar machen?):
                        p = subprocess.Popen(
                            [
                                "ffmpeg",
                                "-y",
                                "-i",
                                target_obj.storage_path,
                                "-vf",
                                "scale=%d:trunc(ow/a/2)*2" % (conversion_size),
                                "-ss",
                                str(new_poster_offset if new_poster_offset else 3),
                                "-vframes",
                                "1",
                                new_tmp_name,
                            ],
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                        )
                    else:
                        raise NotImplementedError("missing operation for conversion: " + str(new_obj.conversion))
                    stdout, stderr = p.communicate()
                    if p.returncode != 0:
                        try:
                            # FIXME: Löschenfunktion nach DBObject ausmodularisieren und Dateibereinigung nach files.File:
                            # Privilege-Escalation damit nicht nur der Eigentümer des Target-Objekts diesen Code ausführen kann:
                            app_old_user = app.user
                            app.user = user.get_admin_user(app)
                            delete_module.delete_in(app, [new_obj.id])
                            app.user = app_old_user
                            os.remove(new_tmp_name)
                            c = app.db.cursor()
                            c.execute(
                                """delete from substitutes where original_id=? and substitute_id=?""",
                                [target_obj.id, new_obj.id],
                            )
                            app.db.commit()
                            results = [x for x in results if x["substitute_id"] != new_obj.id]
                        except Exception as e:
                            error_list.append(e)
                        errmsg = stderr.decode().split("\n")[-1]
                        error_list.append(errors.InternalProgramError(errmsg))
                    else:
                        os.rename(new_tmp_name, new_obj.storage_path)
                        # Fehlerbehandlung:
            if error_list:
                msg = ""
                for error in error_list:
                    if msg:
                        msg += "; "
                    msg += str(error)
                raise errors.InternalProgramError(msg)
            else:
                for result in results:
                    result["substitute_object"] = get_module.get(app, object_ids=[result["substitute_id"]])
                response.output = json.dumps({"succeeded": True, "substitutes": results})
        elif re.match(r"^audio/.*", target_obj.media_type):
            raise NotImplementedError("unsupported media type: " + target_obj.media_type)
        else:
            raise NotImplementedError("unsupported media type: " + target_obj.media_type)
    else:
        raise errors.PrivilegeError()