Commit ea4ea282 authored by Dan Rossi's avatar Dan Rossi
Browse files

- adding cloudfront plugin

parent fefc6ac2
<project name="Flowplayer CloudFront Signed Url" default="deploy">
<property file="${user.home}/plugin.properties" />
<property name="devkit-dir" value="../../lib/devkit" />
<property file="${devkit-dir}/plugin-build.properties" />
<import file="${devkit-dir}/plugin-build.xml"/>
<property name="flowplayer_lib" value="${devkit-dir}/flowplayer.swc" />
<property name="version" value="1.0" />
<property name="basename" value="flowplayer.cloudfrontsignedurl" />
<property name="releasedir" value="${basename}" />
<property name="plugin-binary" value="${basename}.swf" />
<property name="plugin-binary-versioned" value="${basename}-${version}.swf" />
<property name="plugin-main-class" value="org/flowplayer/cloudfrontsignedurl/CloudFrontSignedUrlPlugin.as" />
<property name="library-path" value="lib" />
</project>
\ No newline at end of file
/*
* This file is part of Flowplayer, http://flowplayer.org
*
* By: Thomas Dubois, thomas _at_ flowplayer.org
* Copyright (c) 2010 Flowplayer Oy
*
* Released under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
*/
package org.flowplayer.cloudfrontsignedurl {
import flash.events.NetStatusEvent;
import org.flowplayer.controller.ClipURLResolver;
import org.flowplayer.controller.StreamProvider;
import org.flowplayer.model.Clip;
import org.flowplayer.model.Plugin;
import org.flowplayer.model.PluginModel;
import org.flowplayer.model.PluginError;
import org.flowplayer.util.Log;
import org.flowplayer.util.URLUtil;
import com.adobe.utils.StringUtil;
import org.flowplayer.util.PropertyBinder;
import org.flowplayer.view.Flowplayer;
public class CloudFrontSignedUrl implements ClipURLResolver, Plugin {
private const log:Log = new Log(this);
private var _model:PluginModel;
private var _config:Config;
private var _player:Flowplayer;
private var _signedUrlGenerator:SignedUrlGenerator;
/*
* URL resolving is used for HTTP
*/
public function resolve(provider:StreamProvider, clip:Clip, successListener:Function):void {
var signedUrl:String = _getUrl(clip);
clip.setResolvedUrl(this, signedUrl);
successListener(clip);
}
private function _getUrl(clip:Clip):String {
var url:String = clip.getPreviousResolvedUrl(this);
var isRTMPStream:Boolean = url.toLowerCase().indexOf('mp4:') == 0;
if ( isRTMPStream )
url = url.substr(4);
var expires:Number = Math.round((new Date()).getTime() / 1000 + _config.timeToLive);
var signedUrl:String = _signedUrlGenerator.signUrl(url, expires);
if ( isRTMPStream )
signedUrl = 'mp4:'+ signedUrl;
return signedUrl;
}
private function _checkDomains():Boolean {
if ( _config.domains.length == 0 )
return true;
var url:String = URLUtil.pageUrl;
if ( url == null )
return false;
if ( URLUtil.localDomain(url) )
return true;
var domain:String = _getDomain();
domain = domain.toLowerCase();
var strippedDomain:String = _stripSubdomain(domain);
log.debug("Found domain "+ domain + " "+ strippedDomain);
return _config.domains.indexOf(domain) != -1 || _config.domains.indexOf(strippedDomain) != -1;
}
private function _getDomain():String {
var url:String = URLUtil.pageUrl;
var domain:String = url.substr(url.indexOf('://')+3);
if ( domain.indexOf('/') != -1 )
domain = domain.substr(0, domain.indexOf('/'));
if ( domain.indexOf(':') != -1 )
domain = domain.substr(0, domain.indexOf(':'));
return domain;
}
private function _stripSubdomain(domain:String):String {
var els:Array = domain.split(".");
var len:int = els.length;
// 2 parts --> cannot strip
if (len == 2) return domain;
if ("co,com,net,org,web,gov,edu,".indexOf(els[len-2] + ",") != -1 || StringUtil.endsWith(domain, "ac.uk")) {
// special ending (amazon.co.uk)
return els[len-3] + "." + els[len-2] + "." + els[len-1];;
} else {
return els[len-2] + "." + els[len-1];
}
}
public function set onFailure(listener:Function):void {
}
public function handeNetStatusEvent(event:NetStatusEvent):Boolean {
log.error("handeNetStatusEvent", event);
return true;
}
public function onConfig(model:PluginModel):void {
_model = model;
_config = new Config();
if ( ! model.config )
return;
// only allow to overide default config.
if ( ! _config.privateKey )
_config.privateKey = model.config['privateKey'];
if ( _config.domains.length == 0 )
_config.domains = model.config['domains'];
if ( ! _config.keyPairId )
_config.keyPairId = model.config['keyPairId'];
if ( ! _config.timeToLive )
_config.timeToLive = model.config['timeToLive'];
}
public function onLoad(player:Flowplayer):void {
_player = player;
try {
if ( ! _checkDomains() ) {
_model.dispatchError(PluginError.ERROR, "Incorrect domain : " + _getDomain() +", allowed : "+ _config.domains.join(", ") +")");
return;
}
} catch(e:Error) {
log.error("error", e);
}
_signedUrlGenerator = new SignedUrlGenerator(_config);
_model.dispatchOnLoad();
}
public function getDefaultConfig():Object {
return null;
}
}
}
\ No newline at end of file
/*
* This file is part of Flowplayer, http://flowplayer.org
*
* By: Thomas Dubois, thomas _at_ flowplayer.org
* Copyright (c) 2010 Flowplayer Oy
*
* Released under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
*/
package org.flowplayer.cloudfrontsignedurl {
import flash.display.Sprite;
import org.flowplayer.model.PluginFactory;
public class CloudFrontSignedUrlPlugin extends Sprite implements PluginFactory{
public function newPlugin():Object {
return new CloudFrontSignedUrl();
}
}
}
\ No newline at end of file
/*
* This file is part of Flowplayer, http://flowplayer.org
*
* By: Thomas Dubois, thomas _at_ flowplayer.org
* Copyright (c) 2010 Flowplayer Oy
*
* Released under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
*/
package org.flowplayer.cloudfrontsignedurl {
public class Config {
private var _privateKey:String = "";
private var _keyPairId:String = "";
private var _timeToLive:Number = 5*60;
private var _domains:Array = [];
public function get privateKey():String {
return _privateKey;
}
public function set privateKey(val:String):void {
_privateKey = val;
}
public function get keyPairId():String {
return _keyPairId;
}
public function set keyPairId(val:String):void {
_keyPairId = val;
}
public function toString():String {
return "[Config] keyPairId = '" + _keyPairId + "'";
}
public function get timeToLive():Number {
return _timeToLive;
}
public function set timeToLive(val:Number):void {
_timeToLive = val;
}
public function get domains():Array {
return _domains;
}
public function set domains(value:Array):void {
_domains = value;
}
}
}
\ No newline at end of file
/*
* Copyright 2008 Anssi Piirainen
*
* This file is part of FlowPlayer.
*
* FlowPlayer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FlowPlayer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FlowPlayer. If not, see <http://www.gnu.org/licenses/>.
*/
package org.flowplayer.cloudfrontsignedurl {
import org.flowplayer.controller.*;
/**
* @author api
*/
public class SecuredCloudfrontRTMPConnectionProvider extends ParallelRTMPConnectionProvider {
protected var _config:Object;
public function SecuredCloudfrontRTMPConnectionProvider(config:Object) {
super(config.netConnectionUrl, config.proxyType, config.failOverDelay);
_config = config;
}
}
}
/*
* This file is part of Flowplayer, http://flowplayer.org
*
* By: Thomas Dubois, thomas _at_ flowplayer.org
* Copyright (c) 2010 Flowplayer Oy
*
* Released under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
*/
package org.flowplayer.cloudfrontsignedurl {
import org.flowplayer.model.Clip;
import com.hurlant.crypto.rsa.RSAKey;
import com.hurlant.crypto.hash.SHA1;
import flash.utils.ByteArray;
import com.hurlant.util.Hex;
import com.hurlant.util.der.PEM;
import com.hurlant.util.Base64;
import org.flowplayer.util.Log;
public class SignedUrlGenerator {
private var log:Log = new Log(this);
private var _config:Config = null;
private var _rsa:RSAKey = null;
public function SignedUrlGenerator(config:Config) {
_config = config;
_rsa = PEM.readRSAPrivateKey(_config.privateKey);
}
public function signUrl(url:String, expires:Number):String {
var resource:String = url;
var rawPolicy:String = '{"Statement":[{"Resource":"'+ url +'","Condition":{"DateLessThan":{"AWS:EpochTime":'+ expires +'}}}]}';
log.debug("Using policy "+ rawPolicy);
var safePolicy:String = getSafeString(rawPolicy);
var signature:String = getSignature(rawPolicy);
var safeSignature:String= getSafeString(signature);
var queryString:String = 'Expires='+ expires +'&Signature='+ safeSignature +'&Key-Pair-Id='+ _config.keyPairId;
var separator:String = resource.indexOf('?') == -1 ? '?' : '&';
var signedUrl:String = url + separator + queryString;
log.debug("Generated url "+ signedUrl);
return signedUrl;
}
private function getSignature(rawPolicy:String):String {
var hexRawPolicy:ByteArray = Hex.toArray(Hex.fromString(rawPolicy));
var dst:ByteArray = new ByteArray();
_rsa.sign(hexRawPolicy, dst, hexRawPolicy.length, SHA1DigestPadding);
var signature:String = com.hurlant.util.Base64.encodeByteArray(dst);
return signature;
}
// thousand thanks to Kenji Urushima, derived from http://www9.atwiki.jp/kurushima/pub/jsrsa/
private function SHA1DigestPadding(src:ByteArray, end:int, n:uint, type:uint = 0x02):ByteArray {
var pmStrLen:Number = _rsa.n.bitLength() / 4;
var _RSASIGN_SHA1_DIHEAD:String = "3021300906052b0e03021a05000414";
var sha1:SHA1 = new SHA1();
var sHashHex:ByteArray = sha1.hash(src);
var sHead:String = "0001";
var sTail:String = "00" + _RSASIGN_SHA1_DIHEAD + Hex.fromArray(sHashHex);
var sMid:String = "";
var fLen:Number = pmStrLen - sHead.length - sTail.length;
for (var i:int = 0; i < fLen; i += 2) {
sMid += "ff";
}
var sPaddedMessageHex:String = sHead + sMid + sTail;
return Hex.toArray(sPaddedMessageHex);
}
private function getSafeString(str:String):String {
return str.replace(/\+/g, '-').replace(/=/g, '_').replace(/\//g, '~');
}
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment