diff --git a/build_date.as b/build_date.as index ab73fe4..86caa49 100644 --- a/build_date.as +++ b/build_date.as @@ -1 +1 @@ -public var build_date:String='2020_12_29'; +public var build_date:String='2024_06_05'; diff --git a/com/adobe/protocols/oauth2/OAuth2.as b/com/adobe/protocols/oauth2/OAuth2.as new file mode 100644 index 0000000..92f9942 --- /dev/null +++ b/com/adobe/protocols/oauth2/OAuth2.as @@ -0,0 +1,551 @@ +package com.adobe.protocols.oauth2 +{ + import com.adobe.protocols.oauth2.event.GetAccessTokenEvent; + import com.adobe.protocols.oauth2.event.RefreshAccessTokenEvent; + import com.adobe.protocols.oauth2.grant.AuthorizationCodeGrant; + import com.adobe.protocols.oauth2.grant.IGrantType; + import com.adobe.protocols.oauth2.grant.ImplicitGrant; + import com.adobe.protocols.oauth2.grant.ResourceOwnerCredentialsGrant; + import com.adobe.serialization.json.JSONParseError; + + import flash.events.ErrorEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.events.IOErrorEvent; + import flash.events.LocationChangeEvent; + import flash.events.SecurityErrorEvent; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.net.URLRequestMethod; + import flash.net.URLVariables; + + import org.as3commons.logging.api.ILogger; + import org.as3commons.logging.api.LOGGER_FACTORY; + import org.as3commons.logging.api.getLogger; + import org.as3commons.logging.setup.LevelTargetSetup; + import org.as3commons.logging.setup.LogSetupLevel; + import org.as3commons.logging.setup.target.TraceTarget; + + /** + * Event that is broadcast when results from a getAccessToken request are received. + * + * @eventType com.adobe.protocols.oauth2.event.GetAccessTokenEvent.TYPE + * + * @see #getAccessToken() + * @see com.adobe.protocols.oauth2.event.GetAccessTokenEvent + */ + [Event(name="getAccessToken", type="com.adobe.protocols.oauth2.event.GetAccessTokenEvent")] + + /** + * Event that is broadcast when results from a refreshAccessToken request are received. + * + * @eventType com.adobe.protocols.oauth2.event.RefreshAccessTokenEvent.TYPE + * + * @see #refreshAccessToken() + * @see com.adobe.protocols.oauth2.event.RefreshAccessTokenEvent + */ + [Event(name="refreshAccessToken", type="com.adobe.protocols.oauth2.event.RefreshAccessTokenEvent")] + + /** + * Utility class the encapsulates APIs for interaction with an OAuth 2.0 server. + * Implemented against the OAuth 2.0 v2.15 specification. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15 + * + * @author Charles Bihis (www.whoischarles.com) + */ + public class OAuth2 extends EventDispatcher + { + private static const log:ILogger = getLogger(OAuth2); + + private var grantType:IGrantType; + private var authEndpoint:String; + private var tokenEndpoint:String; + private var traceTarget:TraceTarget = new TraceTarget(); + + + /** + * Constructor to create a valid OAuth2 client object. + * + * @param authEndpoint The authorization endpoint used by the OAuth 2.0 server + * @param tokenEndpoint The token endpoint used by the OAuth 2.0 server + * @param logLevel (Optional) The new log level for the logger to use + */ + public function OAuth2(authEndpoint:String, tokenEndpoint:String, logLevel:LogSetupLevel = null) + { + // save endpoint properties + this.authEndpoint = authEndpoint; + this.tokenEndpoint = tokenEndpoint; + + // set up logging + traceTarget = new TraceTarget(); + traceTarget.format = "{date} {time} [{logLevel}] {name} {message}"; + LOGGER_FACTORY.setup = new LevelTargetSetup(traceTarget, (logLevel == null) ? LogSetupLevel.NONE : logLevel); + } // OAuth2 + + /** + * Initiates the access token request workflow with the proper context as + * described by the passed-in grant-type object. Upon completion, will + * dispatch a GetAccessTokenEvent event. + * + * @param grantType An IGrantType object which represents the desired workflow to use when requesting an access token + * + * @see com.adobe.protocols.oauth2.grant.IGrantType + * @see com.adobe.protocols.oauth2.event.GetAccessTokenEvent#TYPE + */ + public function getAccessToken(grantType:IGrantType):void + { + if (grantType is AuthorizationCodeGrant) + { + log.info("Initiating getAccessToken() with authorization code grant type workflow"); + getAccessTokenWithAuthorizationCodeGrant(grantType as AuthorizationCodeGrant); + } // if statement + else if (grantType is ImplicitGrant) + { + log.info("Initiating getAccessToken() with implicit grant type workflow"); + getAccessTokenWithImplicitGrant(grantType as ImplicitGrant); + } // else-if statement + else if (grantType is ResourceOwnerCredentialsGrant) + { + log.info("Initiating getAccessToken() with resource owner credentials grant type workflow"); + getAccessTokenWithResourceOwnerCredentialsGrant(grantType as ResourceOwnerCredentialsGrant); + } // else-if statement + } // getAccessToken + + /** + * Initiates request to refresh a given access token. Upon completion, will dispatch + * a RefreshAccessTokenEvent event. On success, a new refresh token may + * be issues, at which point the client should discard the old refresh token with the + * new one. + * + * @param refreshToken A valid refresh token received during last request for an access token + * @param clientId The client identifier + * @param clientSecret The client secret + * + * @see com.adobe.protocols.oauth2.event.RefreshAccessTokenEvent#TYPE + */ + public function refreshAccessToken(refreshToken:String, clientId:String, clientSecret:String, scope:String = null):void + { + // create result event + var refreshAccessTokenEvent:RefreshAccessTokenEvent = new RefreshAccessTokenEvent(); + + // set up URL request + var urlRequest:URLRequest = new URLRequest(tokenEndpoint); + var urlLoader:URLLoader = new URLLoader(); + urlRequest.method = URLRequestMethod.POST; + + // define POST parameters + var urlVariables : URLVariables = new URLVariables(); + urlVariables.grant_type = OAuth2Const.GRANT_TYPE_REFRESH_TOKEN; + urlVariables.client_id = clientId; + urlVariables.client_secret = clientSecret; + urlVariables.refresh_token = refreshToken; + + // define optional scope parameter only when scope not null + if(scope !== null) + { + urlVariables.scope = scope; + } + + urlRequest.data = urlVariables; + + // attach event listeners + urlLoader.addEventListener(Event.COMPLETE, onRefreshAccessTokenResult); + urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onRefreshAccessTokenError); + urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onRefreshAccessTokenError); + + // make the call + try + { + urlLoader.load(urlRequest); + } // try statement + catch (error:Error) + { + log.error("Error loading token endpoint \"" + tokenEndpoint + "\""); + } // catch statement + + function onRefreshAccessTokenResult(event:Event):void + { + try + { + var response:Object = com.adobe.serialization.json.JSON.decode(event.target.data); + log.debug("Access token: " + response.access_token); + refreshAccessTokenEvent.parseAccessTokenResponse(response); + } // try statement + catch (error:JSONParseError) + { + refreshAccessTokenEvent.errorCode = "com.adobe.serialization.json.JSONParseError"; + refreshAccessTokenEvent.errorMessage = "Error parsing output from refresh access token response"; + } // catch statement + + dispatchEvent(refreshAccessTokenEvent); + } // onRefreshAccessTokenResult + + function onRefreshAccessTokenError(event:Event):void + { + log.error("Error encountered during refresh access token request: " + event); + + try + { + var error:Object = com.adobe.serialization.json.JSON.decode(event.target.data); + refreshAccessTokenEvent.errorCode = error.error; + refreshAccessTokenEvent.errorMessage = error.error_description; + } // try statement + catch (error:JSONParseError) + { + refreshAccessTokenEvent.errorCode = "Unknown"; + refreshAccessTokenEvent.errorMessage = "Error encountered during refresh access token request. Unable to parse error message."; + } // catch statement + + dispatchEvent(refreshAccessTokenEvent); + } // onRefreshAccessTokenError + } // refreshAccessToken + + /** + * Modifies the log level of the logger at runtime. + * + *

By default, logging is turned off. Passing in any value will modify the logging level + * of the application. This method can accept any of the following values...

+ * + * + * + * @param logLevel The new log level for the logger to use + * + * @see org.as3commons.logging.setup.LogSetupLevel.NONE + * @see org.as3commons.logging.setup.LogSetupLevel.FATAL + * @see org.as3commons.logging.setup.LogSetupLevel.FATAL_ONLY + * @see org.as3commons.logging.setup.LogSetupLevel.ERROR + * @see org.as3commons.logging.setup.LogSetupLevel.ERROR_ONLY + * @see org.as3commons.logging.setup.LogSetupLevel.WARN + * @see org.as3commons.logging.setup.LogSetupLevel.WARN_ONLY + * @see org.as3commons.logging.setup.LogSetupLevel.INFO + * @see org.as3commons.logging.setup.LogSetupLevel.INFO_ONLY + * @see org.as3commons.logging.setup.LogSetupLevel.DEBUG + * @see org.as3commons.logging.setup.LogSetupLevel.DEBUG_ONLY + * @see org.as3commons.logging.setup.LogSetupLevel.ALL + */ + public function setLogLevel(logLevel:LogSetupLevel):void + { + LOGGER_FACTORY.setup = new LevelTargetSetup(traceTarget, logLevel); + } // setLogLevel + + /** + * @private + * + * Helper function that completes get-access-token request using the authorization code grant type. + */ + private function getAccessTokenWithAuthorizationCodeGrant(authorizationCodeGrant:AuthorizationCodeGrant):void + { + // create result event + var getAccessTokenEvent:GetAccessTokenEvent = new GetAccessTokenEvent(); + + // add event listeners + authorizationCodeGrant.stageWebView.addEventListener(LocationChangeEvent.LOCATION_CHANGING, onLocationChanging); + authorizationCodeGrant.stageWebView.addEventListener(LocationChangeEvent.LOCATION_CHANGE, onLocationChanging); + authorizationCodeGrant.stageWebView.addEventListener(Event.COMPLETE, onStageWebViewComplete); + authorizationCodeGrant.stageWebView.addEventListener(ErrorEvent.ERROR, onStageWebViewError); + + // start the auth process + var startTime:Number = new Date().time; + log.info("Loading auth URL: " + authorizationCodeGrant.getFullAuthUrl(authEndpoint)); + authorizationCodeGrant.stageWebView.loadURL(authorizationCodeGrant.getFullAuthUrl(authEndpoint)); + + function onLocationChanging(locationChangeEvent:LocationChangeEvent):void + { + log.info("Loading URL: " + locationChangeEvent.location); + if (locationChangeEvent.location.indexOf(authorizationCodeGrant.redirectUri) == 0 && locationChangeEvent.location.indexOf(OAuth2Const.RESPONSE_PROPERTY_AUTHORIZATION_CODE) > 0) + { + log.info("Redirect URI encountered (" + authorizationCodeGrant.redirectUri + "). Extracting values from path."); + + // stop event from propogating + locationChangeEvent.preventDefault(); + + // determine if authorization was successful + var queryParams:Object = extractQueryParams(locationChangeEvent.location); + var code:String = queryParams.code; // authorization code + if (code != null) + { + log.debug("Authorization code: " + code); + getAccessTokenWithAuthCode(code); + } // if statement + else + { + log.error("Error encountered during authorization request"); + getAccessTokenEvent.errorCode = queryParams.error; + getAccessTokenEvent.errorMessage = queryParams.error_description; + dispatchEvent(getAccessTokenEvent); + } // else statement + } // if statement + } // onLocationChange + + function getAccessTokenWithAuthCode(code:String):void + { + // set up URL request + var urlRequest:URLRequest = new URLRequest(tokenEndpoint); + var urlLoader:URLLoader = new URLLoader(); + urlRequest.method = URLRequestMethod.POST; + + // define POST parameters + var urlVariables : URLVariables = new URLVariables(); + urlVariables.grant_type = OAuth2Const.GRANT_TYPE_AUTHORIZATION_CODE; + urlVariables.code = code; + urlVariables.redirect_uri = authorizationCodeGrant.redirectUri; + urlVariables.client_id = authorizationCodeGrant.clientId; + urlVariables.client_secret = authorizationCodeGrant.clientSecret; + urlRequest.data = urlVariables; + + // attach event listeners + urlLoader.addEventListener(Event.COMPLETE, onGetAccessTokenResult); + urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onGetAccessTokenError); + urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onGetAccessTokenError); + + // make the call + try + { + urlLoader.load(urlRequest); + } // try statement + catch (error:Error) + { + log.error("Error loading token endpoint \"" + tokenEndpoint + "\""); + } // catch statement + + function onGetAccessTokenResult(event:Event):void + { + try + { + var response:Object = com.adobe.serialization.json.JSON.decode(event.target.data); + log.debug("Access token: " + response.access_token); + getAccessTokenEvent.parseAccessTokenResponse(response); + } // try statement + catch (error:JSONParseError) + { + getAccessTokenEvent.errorCode = "com.adobe.serialization.json.JSONParseError"; + getAccessTokenEvent.errorMessage = "Error parsing output from access token response"; + } // catch statement + + dispatchEvent(getAccessTokenEvent); + } // onGetAccessTokenResult + + function onGetAccessTokenError(event:Event):void + { + log.error("Error encountered during access token request: " + event); + + try + { + var error:Object = com.adobe.serialization.json.JSON.decode(event.target.data); + getAccessTokenEvent.errorCode = error.error; + getAccessTokenEvent.errorMessage = error.error_description; + } // try statement + catch (error:JSONParseError) + { + getAccessTokenEvent.errorCode = "Unknown"; + getAccessTokenEvent.errorMessage = "Error encountered during access token request. Unable to parse error message."; + } // catch statement + + dispatchEvent(getAccessTokenEvent); + } // onGetAccessTokenError + } // getAccessTokenWithAuthCode + + function onStageWebViewComplete(event:Event):void + { + // Note: Special provision made particularly for Google OAuth 2 implementation for installed + // applications. Particularly, when we see a certain redirect URI, we must look for the authorization + // code in the page title as opposed to in the URL. See https://developers.google.com/accounts/docs/OAuth2InstalledApp#choosingredirecturi + // for more information. + if (authorizationCodeGrant.redirectUri == OAuth2Const.GOOGLE_INSTALLED_APPLICATION_REDIRECT_URI && event.currentTarget.title.indexOf(OAuth2Const.RESPONSE_TYPE_AUTHORIZATION_CODE) > 0) + { + var codeString:String = event.currentTarget.title.substring(event.currentTarget.title.indexOf(OAuth2Const.RESPONSE_TYPE_AUTHORIZATION_CODE)); + var code:String = codeString.split("=")[1]; + log.debug("Authorization code extracted from page title: " + code); + getAccessTokenWithAuthCode(code); + } + else + { + log.info("Auth URL loading complete after " + (new Date().time - startTime) + "ms"); + } + } // onStageWebViewComplete + + function onStageWebViewError(errorEvent:ErrorEvent):void + { + log.error("Error occurred with StageWebView: " + errorEvent); + getAccessTokenEvent.errorCode = "STAGE_WEB_VIEW_ERROR"; + getAccessTokenEvent.errorMessage = "Error occurred with StageWebView"; + dispatchEvent(getAccessTokenEvent); + } // onStageWebViewError + } // getAccessTokenWithAuthorizationCodeGrant + + /** + * @private + * + * Helper function that completes get-access-token request using the implicit grant type. + */ + private function getAccessTokenWithImplicitGrant(implicitGrant:ImplicitGrant):void + { + // create result event + var getAccessTokenEvent:GetAccessTokenEvent = new GetAccessTokenEvent(); + + // add event listeners + implicitGrant.stageWebView.addEventListener(LocationChangeEvent.LOCATION_CHANGING, onLocationChange); + implicitGrant.stageWebView.addEventListener(LocationChangeEvent.LOCATION_CHANGE, onLocationChange); + implicitGrant.stageWebView.addEventListener(ErrorEvent.ERROR, onStageWebViewError); + + // start the auth process + log.info("Loading auth URL: " + implicitGrant.getFullAuthUrl(authEndpoint)); + implicitGrant.stageWebView.loadURL(implicitGrant.getFullAuthUrl(authEndpoint)); + + function onLocationChange(locationChangeEvent:LocationChangeEvent):void + { + log.info("Loading URL: " + locationChangeEvent.location); + if (locationChangeEvent.location.indexOf(implicitGrant.redirectUri) == 0 && locationChangeEvent.location.indexOf(OAuth2Const.RESPONSE_PROPERTY_ACCESS_TOKEN) > 0) + { + log.info("Redirect URI encountered (" + implicitGrant.redirectUri + "). Extracting values from path."); + + // stop event from propogating + locationChangeEvent.preventDefault(); + + // determine if authorization was successful + var queryParams:Object = extractQueryParams(locationChangeEvent.location); + var accessToken:String = queryParams.access_token; + if (accessToken != null) + { + log.debug("Access token: " + accessToken); + getAccessTokenEvent.parseAccessTokenResponse(queryParams); + dispatchEvent(getAccessTokenEvent); + } // if statement + else + { + log.error("Error encountered during access token request"); + getAccessTokenEvent.errorCode = queryParams.error; + getAccessTokenEvent.errorMessage = queryParams.error_description; + dispatchEvent(getAccessTokenEvent); + } // else statement + } // if statement + } // onLocationChange + + function onStageWebViewError(errorEvent:ErrorEvent):void + { + log.error("Error occurred with StageWebView: " + errorEvent); + getAccessTokenEvent.errorCode = "STAGE_WEB_VIEW_ERROR"; + getAccessTokenEvent.errorMessage = "Error occurred with StageWebView"; + dispatchEvent(getAccessTokenEvent); + } // onStageWebViewError + } // getAccessTokenWithImplicitGrant + + /** + * @private + * + * Helper function that completes get-access-token request using the resource owner password credentials grant type. + */ + private function getAccessTokenWithResourceOwnerCredentialsGrant(resourceOwnerCredentialsGrant:ResourceOwnerCredentialsGrant):void + { + // create result event + var getAccessTokenEvent:GetAccessTokenEvent = new GetAccessTokenEvent(); + + // set up URL request + var urlRequest:URLRequest = new URLRequest(tokenEndpoint); + var urlLoader:URLLoader = new URLLoader(); + urlRequest.method = URLRequestMethod.POST; + + // define POST parameters + var urlVariables : URLVariables = new URLVariables(); + urlVariables.grant_type = OAuth2Const.GRANT_TYPE_RESOURCE_OWNER_CREDENTIALS; + urlVariables.client_id = resourceOwnerCredentialsGrant.clientId; + urlVariables.client_secret = resourceOwnerCredentialsGrant.clientSecret; + urlVariables.username = resourceOwnerCredentialsGrant.username; + urlVariables.password = resourceOwnerCredentialsGrant.password; + + // define optional scope parameter only when scope not null + if(resourceOwnerCredentialsGrant.scope !== null) + { + urlVariables.scope = resourceOwnerCredentialsGrant.scope; + } + + urlRequest.data = urlVariables; + + // attach event listeners + urlLoader.addEventListener(Event.COMPLETE, onGetAccessTokenResult); + urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onGetAccessTokenError); + urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onGetAccessTokenError); + + // make the call + try + { + urlLoader.load(urlRequest); + } // try statement + catch (error:Error) + { + log.error("Error loading token endpoint \"" + tokenEndpoint + "\""); + } // catch statement + + function onGetAccessTokenResult(event:Event):void + { + try + { + var response:Object = com.adobe.serialization.json.JSON.decode(event.target.data); + log.debug("Access token: " + response.access_token); + getAccessTokenEvent.parseAccessTokenResponse(response); + } // try statement + catch (error:JSONParseError) + { + getAccessTokenEvent.errorCode = "com.adobe.serialization.json.JSONParseError"; + getAccessTokenEvent.errorMessage = "Error parsing output from access token response"; + } // catch statement + + dispatchEvent(getAccessTokenEvent); + } // onGetAccessTokenResult + + function onGetAccessTokenError(event:Event):void + { + log.error("Error encountered during access token request: " + event); + + try + { + var error:Object = com.adobe.serialization.json.JSON.decode(event.target.data); + getAccessTokenEvent.errorCode = error.error; + getAccessTokenEvent.errorMessage = error.error_description; + } // try statement + catch (error:JSONParseError) + { + getAccessTokenEvent.errorCode = "Unknown"; + getAccessTokenEvent.errorMessage = "Error encountered during access token request. Unable to parse error message."; + } // catch statement + + dispatchEvent(getAccessTokenEvent); + } // onGetAccessTokenError + } // getAccessTokenWithResourceOwnerCredentialsGrant + + /** + * @private + * + * Helper function to extract query from URL and URL fragment. + */ + private function extractQueryParams(url:String):Object + { + var delimiter:String = (url.indexOf("?") > 0) ? "?" : "#"; + var queryParamsString:String = url.split(delimiter)[1]; + var queryParamsArray:Array = queryParamsString.split("&"); + var queryParams:Object = new Object(); + + for each (var queryParam:String in queryParamsArray) + { + var keyValue:Array = queryParam.split("="); + queryParams[keyValue[0]] = keyValue[1]; + } // for loop + + return queryParams; + } // extractQueryParams + } // class declaration +} // package \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/OAuth2Const.as b/com/adobe/protocols/oauth2/OAuth2Const.as new file mode 100644 index 0000000..616c2b8 --- /dev/null +++ b/com/adobe/protocols/oauth2/OAuth2Const.as @@ -0,0 +1,17 @@ +package com.adobe.protocols.oauth2 +{ + public class OAuth2Const + { + public static const GRANT_TYPE_AUTHORIZATION_CODE:String = "authorization_code"; + public static const GRANT_TYPE_RESOURCE_OWNER_CREDENTIALS:String = "password"; + public static const GRANT_TYPE_REFRESH_TOKEN:String = "refresh_token"; + + public static const RESPONSE_TYPE_AUTHORIZATION_CODE:String = "code"; + public static const RESPONSE_TYPE_IMPLICIT:String = "token"; + + public static const RESPONSE_PROPERTY_AUTHORIZATION_CODE:String = "code"; + public static const RESPONSE_PROPERTY_ACCESS_TOKEN:String = "access_token"; + + public static const GOOGLE_INSTALLED_APPLICATION_REDIRECT_URI:String = "urn:ietf:wg:oauth:2.0:oob"; + } +} \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/event/GetAccessTokenEvent.as b/com/adobe/protocols/oauth2/event/GetAccessTokenEvent.as new file mode 100644 index 0000000..4f912a9 --- /dev/null +++ b/com/adobe/protocols/oauth2/event/GetAccessTokenEvent.as @@ -0,0 +1,173 @@ +package com.adobe.protocols.oauth2.event +{ + import flash.events.Event; + + /** + * Event that is broadcast when results from a getAccessToken + * request are received. + * + * @author Charles Bihis (www.whoischarles.com) + */ + public class GetAccessTokenEvent extends Event implements IOAuth2Event + { + /** + * Event type for this event which encapsulates the response from + * a getAccessToken request. + * + * @eventType getAccessToken + */ + public static const TYPE:String = "getAccessToken"; + + private var _errorCode:String; + private var _errorMessage:String; + private var _accessToken:String; + private var _tokenType:String; + private var _expiresIn:int; + private var _refreshToken:String; + private var _scope:String; + private var _state:String; + private var _response:Object; + + /** + * Constructor. + * + * @param bubbles (Optional) Parameter indicating whether or not the event bubbles + * @param cancelable (Optional Parameter indicating whether or not the event is cancelable + */ + public function GetAccessTokenEvent(bubbles:Boolean = false, cancelable:Boolean = false) + { + super(TYPE, bubbles, cancelable); + } // GetAccessTokenEvent + + /** + * Convenience function that will take a getAccessToken response + * and parse its values. + * + * @param response An object representing the response from a getAccessToken request + */ + public function parseAccessTokenResponse(response:Object):void + { + // required + _accessToken = response.access_token; + _tokenType = response.token_type; + + // optional + _expiresIn = int(response.expires_in); + _refreshToken = response.refresh_token; + _scope = response.scope; + _state = response.state; + + // extra + _response = response; + } + + /** + * Override of the clone function. + * + * @return A new GetAccessTokenEvent object. + */ + public override function clone():Event + { + return new GetAccessTokenEvent(); + } // clone + + /** + * Error code for error after a failed getAccessToken request. + */ + public function get errorCode():String + { + return _errorCode; + } // errorCode + + /** + * @private + */ + public function set errorCode(errorCode:String):void + { + _errorCode = errorCode; + } // errorCode + + /** + * Error message for error after a failed getAccessToken request. + */ + public function get errorMessage():String + { + return _errorMessage; + } // errorMessage + + /** + * @private + */ + public function set errorMessage(errorMessage:String):void + { + _errorMessage = errorMessage; + } // errorMessage + + /** + * The access token issues by the authorization server. + */ + public function get accessToken():String + { + return _accessToken; + } // accessToken + + /** + * The type of the token issued as described in the OAuth 2.0 + * v2.15 specification, section 7.1, "Access Token Types". + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-7.1 + */ + public function get tokenType():String + { + return _tokenType; + } // tokenType + + /** + * The duration in seconds of the access token lifetime. For example, + * the value "3600" denotes that the access token will expire one hour + * from the time the response was generated. + */ + public function get expiresIn():int + { + return _expiresIn; + } // expiresIn + + /** + * The refresh token which can be used ot obtain new access tokens using + * the same authorization grant as described in the OAuth 2.0 + * v2.15 specification, section 6, "Refreshing an Access Token". + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-6 + */ + public function get refreshToken():String + { + return _refreshToken; + } // refreshToken + + /** + * The scope of the access request expressed as a list of space-delimited, + * case-sensitive strings. + */ + public function get scope():String + { + return _scope; + } // scope + + /** + * An opaque value used by the client to maintain state between the request + * and callback. + */ + public function get state():String + { + return _state; + } // state + + /** + * Response object to contain all returned response data after a successfull access token request. + */ + public function get response():Object + { + return _response; + } // response + } // class declaration +} // package \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/event/IOAuth2Event.as b/com/adobe/protocols/oauth2/event/IOAuth2Event.as new file mode 100644 index 0000000..3002690 --- /dev/null +++ b/com/adobe/protocols/oauth2/event/IOAuth2Event.as @@ -0,0 +1,16 @@ +package com.adobe.protocols.oauth2.event +{ + /** + * Interface describing generic OAuth 2.0 events. + * + * @author Charles Bihis (www.whoischarles.com) + */ + public interface IOAuth2Event + { + function get errorCode():String; + function set errorCode(errorCode:String):void; + + function get errorMessage():String; + function set errorMessage(errorMessage:String):void; + } // interface declaration +} // package \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/event/RefreshAccessTokenEvent.as b/com/adobe/protocols/oauth2/event/RefreshAccessTokenEvent.as new file mode 100644 index 0000000..868251b --- /dev/null +++ b/com/adobe/protocols/oauth2/event/RefreshAccessTokenEvent.as @@ -0,0 +1,173 @@ +package com.adobe.protocols.oauth2.event +{ + import flash.events.Event; + + /** + * Event that is broadcast when results from a refreshAccessToken + * request are received. + * + * @author Charles Bihis (www.whoischarles.com) + */ + public class RefreshAccessTokenEvent extends Event implements IOAuth2Event + { + /** + * Event type for this event which encapsulates the response from + * a refreshAccessToken request. + * + * @eventType refreshAccessToken + */ + public static const TYPE:String = "refreshAccessToken"; + + private var _errorCode:String; + private var _errorMessage:String; + private var _accessToken:String; + private var _tokenType:String; + private var _expiresIn:int; + private var _refreshToken:String; + private var _scope:String; + private var _state:String; + private var _response:Object; + + /** + * Constructor. + * + * @param bubbles (Optional) Parameter indicating whether or not the event bubbles + * @param cancelable (Optional Parameter indicating whether or not the event is cancelable + */ + public function RefreshAccessTokenEvent(bubbles:Boolean = false, cancelable:Boolean = false) + { + super(TYPE, bubbles, cancelable); + } // RefreshAccessTokenEvent + + /** + * Convenience function that will take a refreshAccessToken response + * and parse its values. + * + * @param response An object representing the response from a refreshAccessToken request + */ + public function parseAccessTokenResponse(response:Object):void + { + // required + _accessToken = response.access_token; + _tokenType = response.token_type; + + // optional + _expiresIn = int(response.expires_in); + _refreshToken = response.refresh_token; + _scope = response.scope; + _state = response.state; + + // extra + _response = response; + } + + /** + * Override of the clone function. + * + * @return A new RefreshAccessTokenEvent object. + */ + public override function clone():Event + { + return new GetAccessTokenEvent(); + } // clone + + /** + * Error code for error after a failed refreshAccessToken request. + */ + public function get errorCode():String + { + return _errorCode; + } // errorCode + + /** + * @private + */ + public function set errorCode(errorCode:String):void + { + _errorCode = errorCode; + } // errorCode + + /** + * Error message for error after a failed refreshAccessToken request. + */ + public function get errorMessage():String + { + return _errorMessage; + } // errorMessage + + /** + * @private + */ + public function set errorMessage(errorMessage:String):void + { + _errorMessage = errorMessage; + } // errorMessage + + /** + * The access token issues by the authorization server. + */ + public function get accessToken():String + { + return _accessToken; + } // accessToken + + /** + * The type of the token issued as described in the OAuth 2.0 + * v2.15 specification, section 7.1, "Access Token Types". + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-7.1 + */ + public function get tokenType():String + { + return _tokenType; + } // tokenType + + /** + * The duration in seconds of the access token lifetime. For example, + * the value "3600" denotes that the access token will expire one hour + * from the time the response was generated. + */ + public function get expiresIn():int + { + return _expiresIn; + } // expiresIn + + /** + * The refresh token which can be used ot obtain new access tokens using + * the same authorization grant as described in the OAuth 2.0 + * v2.15 specification, section 6, "Refreshing an Access Token". + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-6 + */ + public function get refreshToken():String + { + return _refreshToken; + } // refreshToken + + /** + * The scope of the access request expressed as a list of space-delimited, + * case-sensitive strings. + */ + public function get scope():String + { + return _scope; + } // scope + + /** + * An opaque value used by the client to maintain state between the request + * and callback. + */ + public function get state():String + { + return _state; + } // state + + /** + * Response object to contain all returned response data after a successfull access token request. + */ + public function get response():Object + { + return _response; + } // response + } // class declaration +} // package \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/grant/AuthorizationCodeGrant.as b/com/adobe/protocols/oauth2/grant/AuthorizationCodeGrant.as new file mode 100644 index 0000000..3115196 --- /dev/null +++ b/com/adobe/protocols/oauth2/grant/AuthorizationCodeGrant.as @@ -0,0 +1,140 @@ +package com.adobe.protocols.oauth2.grant +{ + import com.adobe.protocols.oauth2.OAuth2Const; + + import flash.media.StageWebView; + + /** + * Class to encapsulate all of the relevant properties used during + * a get-access-token request using the authorization code grant type. + * + * @author Charles Bihis (www.whoischarles.com) + */ + public class AuthorizationCodeGrant implements IGrantType + { + private var _stageWebView:StageWebView; + private var _clientId:String; + private var _clientSecret:String; + private var _redirectUri:String; + private var _scope:String; + private var _state:Object; + private var _queryParams:Object; + + /** + * Constructor. + * + * @param stageWebView The StageWebView object for which to display the user-consent page + * @param clientId The client identifier + * @param clientSecret The client secret + * @param redirectUri The redirect URI to return to after the authorization process has completed + * @param scope (Optional) The scope of the access request expressed as a list of space-delimited, case-sensitive strings + * @param state (Optional) An opaque value used by the client to maintain state between the request and callback + * @param queryParams (Optional) Additional query parameters that can be passed to the authorization URL + */ + public function AuthorizationCodeGrant(stageWebView:StageWebView, clientId:String, clientSecret:String, redirectUri:String, scope:String = null, state:Object = null, queryParams:Object = null) + { + _stageWebView = stageWebView; + _clientId = clientId; + _clientSecret = clientSecret; + _redirectUri = redirectUri; + _scope = scope; + _state = state; + _queryParams = queryParams; + } // AuthorizationCodeGrant + + /** + * The StageWebView object for which to display the user-consent page. + */ + public function get stageWebView():StageWebView + { + return _stageWebView; + } // stageWebView + + /** + * The client identifier as described in the OAuth spec v2.15, + * section 3, Client Authentication. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-3 + */ + public function get clientId():String + { + return _clientId; + } // clientId + + /** + * The client secret. + */ + public function get clientSecret():String + { + return _clientSecret; + } // clientSecret + + /** + * The redirect endpoint for the client as described in the OAuth + * spec v2.15, section 3.1.2, Redirection Endpoint. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1.2 + */ + public function get redirectUri():String + { + return _redirectUri; + } // redirectUri + + /** + * The scope of the access request expressed as a list of space-delimited, + * case-sensitive strings. + */ + public function get scope():String + { + return _scope; + } // scope + + /** + * An opaque value used by the client to maintain state between the request + * and callback. + */ + public function get state():Object + { + return _state; + } // state + + /** + * Additional query parameters that can be passed to the authorization URL. + */ + public function get queryParams():Object + { + return _queryParams; + } // queryParams + + /** + * Convenience method for getting the full authorization URL. + */ + public function getFullAuthUrl(authEndpoint:String):String + { + var url:String = authEndpoint + "?response_type=" + OAuth2Const.RESPONSE_TYPE_AUTHORIZATION_CODE + "&client_id=" + clientId + "&redirect_uri=" + redirectUri; + + // scope is optional + if (scope != null && scope.length > 0) + { + url += "&scope=" + scope; + } // if statement + + // state is optional + if (state != null) + { + url += "&state=" + state; + } // if statement + + // add additional optional query params, if any + if (queryParams != null) + { + for (var queryParam:String in queryParams) + { + url += "&" + queryParam + "=" + queryParams[queryParam]; + } // for loop + } // if statement + + return url; + } // getFullAuthUrl + } // class declaration +} // package \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/grant/IGrantType.as b/com/adobe/protocols/oauth2/grant/IGrantType.as new file mode 100644 index 0000000..0576ad8 --- /dev/null +++ b/com/adobe/protocols/oauth2/grant/IGrantType.as @@ -0,0 +1,17 @@ +package com.adobe.protocols.oauth2.grant +{ + /** + * Interface used as a marker to signify whether a class + * encapsulates the properties of any of the supported + * OAuth 2.0 grant types, as described by the v2.15 + * specification. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4 + * + * @author Charles Bihis (www.whoischarles.com) + */ + public interface IGrantType + { + function get clientId():String; + } // interface declaration +} // package \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/grant/ImplicitGrant.as b/com/adobe/protocols/oauth2/grant/ImplicitGrant.as new file mode 100644 index 0000000..e427f34 --- /dev/null +++ b/com/adobe/protocols/oauth2/grant/ImplicitGrant.as @@ -0,0 +1,129 @@ +package com.adobe.protocols.oauth2.grant +{ + import com.adobe.protocols.oauth2.OAuth2Const; + + import flash.media.StageWebView; + + /** + * Class to encapsulate all of the relevant properties used during + * a get-access-token request using the implicit grant type. + * + * @author Charles Bihis (www.whoischarles.com) + */ + public class ImplicitGrant implements IGrantType + { + private var _stageWebView:StageWebView; + private var _clientId:String; + private var _redirectUri:String; + private var _scope:String; + private var _state:Object; + private var _queryParams:Object; + + /** + * Constructor. + * + * @param stageWebView The StageWebView object for which to display the user-consent page + * @param clientId The client identifier + * @param redirectUri The redirect URI to return to after the authorization process has completed + * @param scope (Optional) The scope of the access request expressed as a list of space-delimited, case-sensitive strings + * @param state (Optional) An opaque value used by the client to maintain state between the request and callback + * @param queryParams (Optional) Additional query parameters that can be passed to the authorization URL + */ + public function ImplicitGrant(stageWebView:StageWebView, clientId:String, redirectUri:String, scope:String = null, state:Object = null, queryParams:Object = null) + { + _stageWebView = stageWebView; + _clientId = clientId; + _redirectUri = redirectUri; + _scope = scope; + _state = state; + _queryParams = queryParams; + } // ImplicitGrant + + /** + * The StageWebView object for which to display the user-consent page. + */ + public function get stageWebView():StageWebView + { + return _stageWebView; + } // stageWebView + + /** + * The client identifier as described in the OAuth spec v2.15, + * section 3, Client Authentication. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-3 + */ + public function get clientId():String + { + return _clientId; + } // clientId + + /** + * The redirect endpoint for the client as described in the OAuth + * spec v2.15, section 3.1.2, Redirection Endpoint. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-20#section-3.1.2 + */ + public function get redirectUri():String + { + return _redirectUri; + } // redirectUri + + /** + * The scope of the access request expressed as a list of space-delimited, + * case-sensitive strings. + */ + public function get scope():String + { + return _scope; + } // scope + + /** + * An opaque value used by the client to maintain state between the request + * and callback. + */ + public function get state():Object + { + return _state; + } // state + + /** + * Additional query parameters that can be passed to the authorization URL. + */ + public function get queryParams():Object + { + return _queryParams; + } // queryParams + + /** + * Convenience method for getting the full authorization URL. + */ + public function getFullAuthUrl(endpoint:String):String + { + var url:String = endpoint + "?response_type=" + OAuth2Const.RESPONSE_TYPE_IMPLICIT + "&client_id=" + clientId + "&redirect_uri=" + redirectUri; + + // scope is optional + if (scope != null && scope.length > 0) + { + url += "&scope=" + scope; + } // if statement + + // state is optional + if (state != null) + { + url += "&state=" + state; + } // if statement + + // add additional optional query params, if any + if (queryParams != null) + { + for (var queryParam:String in queryParams) + { + url += "&" + queryParam + "=" + queryParams[queryParam]; + } // for loop + } // if statement + + return url; + } // getFullAuthUrl + } // class declaration +} // package \ No newline at end of file diff --git a/com/adobe/protocols/oauth2/grant/ResourceOwnerCredentialsGrant.as b/com/adobe/protocols/oauth2/grant/ResourceOwnerCredentialsGrant.as new file mode 100644 index 0000000..cabc1b5 --- /dev/null +++ b/com/adobe/protocols/oauth2/grant/ResourceOwnerCredentialsGrant.as @@ -0,0 +1,80 @@ +package com.adobe.protocols.oauth2.grant +{ + /** + * Class to encapsulate all of the relevant properties used during + * a get-access-token request using the resource owner password + * credentials grant type. + * + * @author Charles Bihis (www.hoischarles.com) + */ + public class ResourceOwnerCredentialsGrant implements IGrantType + { + private var _clientId:String; + private var _clientSecret:String; + private var _username:String; + private var _password:String; + private var _scope:String; + + /** + * Constructor. + * + * @param clientId The client identifier + * @param clientSecret The client secret + * @param username The resource owner's username for the authorization server + * @param password The resource owner's password for the authorization server + * @param scope (Optional) The scope of the access request expressed as a list of space-delimited, case-sensitive strings + */ + public function ResourceOwnerCredentialsGrant(clientId:String, clientSecret:String, username:String, password:String, scope:String = null) + { + _clientId = clientId; + _clientSecret = clientSecret; + _username = username; + _password = password; + _scope = scope; + } // ResourceOwnserCredentialsGrant + + /** + * The client identifier as described in the OAuth spec v2.15, + * section 3, Client Authentication. + * + * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-3 + */ + public function get clientId():String + { + return _clientId; + } // clientId + + /** + * The client secret. + */ + public function get clientSecret():String + { + return _clientSecret; + } // clientSecret + + /** + * The resource owners username. + */ + public function get username():String + { + return _username; + } // username + + /** + * The resource owner password. + */ + public function get password():String + { + return _password; + } // password + + /** + * The scope of the access request expressed as a list of space-delimited, + * case-sensitive strings. + */ + public function get scope():String + { + return _scope; + } // scope + } // class declaration +} // package \ No newline at end of file diff --git a/lib/as3commons-logging-2.7.swc b/lib/as3commons-logging-2.7.swc new file mode 100644 index 0000000..0266508 Binary files /dev/null and b/lib/as3commons-logging-2.7.swc differ diff --git a/lib/iotashan-oath.swc b/lib/iotashan-oath.swc deleted file mode 100644 index 28d6030..0000000 Binary files a/lib/iotashan-oath.swc and /dev/null differ diff --git a/net/systemeD/halcyon/WayUI.as b/net/systemeD/halcyon/WayUI.as index b2e231d..55eec1a 100644 --- a/net/systemeD/halcyon/WayUI.as +++ b/net/systemeD/halcyon/WayUI.as @@ -588,7 +588,7 @@ package net.systemeD.halcyon { var t1:Number = (pathlength/2 - tf.width/2) / pathlength; var p1:Array=pointAt(t1); var t2:Number = (pathlength/2 + tf.width/2) / pathlength; var p2:Array=pointAt(t2); - var mult:Number = (Capabilities.playerType == "Desktop") ? Number(nameformat.size)*1.8 : 1; + var mult:Number = (Capabilities.playerType == "Desktop") ? Number(nameformat.size) : 1; var angleOffset:Number; // so we can do a 180ยบ if we're running backwards var offsetSign:Number; // -1 if we're starting at t2 diff --git a/net/systemeD/halcyon/connection/Connection.as b/net/systemeD/halcyon/connection/Connection.as index bf66a51..04df4ec 100644 --- a/net/systemeD/halcyon/connection/Connection.as +++ b/net/systemeD/halcyon/connection/Connection.as @@ -39,11 +39,6 @@ package net.systemeD.halcyon.connection { public function set apiBase(api:String):void { apiBaseURL = api; } public function get apiDomain():String { return apiBaseURL.replace(/(^https?:\/\/.+?\/).+$/, "$1"); } - // OAuth getters - public function get oauthRequestToken():String { return apiDomain+"oauth/request_token"; } - public function get oauthAccessToken():String { return apiDomain+"oauth/access_token"; } - public function get oauthAuthURL():String { return apiDomain+"oauth/authorize"; } - // connection events public static var LOAD_STARTED:String = "load_started"; public static var LOAD_COMPLETED:String = "load_completed"; @@ -640,21 +635,22 @@ package net.systemeD.halcyon.connection { top:Number, bottom:Number):void { } public function loadEntityByID(type:String, id:Number):void {} - public function setAuthToken(id:Object):void {} - public function deleteAuthToken():void {} - public function setAccessToken(key:String, secret:String):void {} public function createChangeset(tags:Object):void {} public function closeChangeset(callback:Function=null):void {} public function uploadChanges(closeAfterwards:Boolean=false):* {} public function fetchUserTraces(refresh:Boolean=false):void {} public function fetchTrace(id:Number, callback:Function):void {} - public function hasAccessToken():Boolean { return false; } public function fetchHistory(entity:Entity, callback:Function):void {} - public function loadEntity(entity:Entity):void { loadEntityByID(entity.getType(),entity.id); } - + + // OAuth2 support + public var oauthAccessToken:String; // Access token, should be set either from SharedObject (cookie) or from osm.org response + public function setAccessToken(token:String):void {} + public function hasAccessToken():Boolean { return false; } + public function getAccessToken():String { return ""; } + public function deleteAccessToken():void {} } } diff --git a/net/systemeD/halcyon/connection/XMLBaseConnection.as b/net/systemeD/halcyon/connection/XMLBaseConnection.as index 967e143..97e8e08 100644 --- a/net/systemeD/halcyon/connection/XMLBaseConnection.as +++ b/net/systemeD/halcyon/connection/XMLBaseConnection.as @@ -4,7 +4,6 @@ package net.systemeD.halcyon.connection { import flash.system.Security; import flash.net.*; - import org.iotashan.oauth.*; import net.systemeD.halcyon.MapEvent; import net.systemeD.halcyon.connection.bboxes.*; diff --git a/net/systemeD/halcyon/connection/XMLConnection.as b/net/systemeD/halcyon/connection/XMLConnection.as index 07d8c1d..92f8f7d 100644 --- a/net/systemeD/halcyon/connection/XMLConnection.as +++ b/net/systemeD/halcyon/connection/XMLConnection.as @@ -5,7 +5,6 @@ package net.systemeD.halcyon.connection { import mx.rpc.events.*; import flash.system.Security; import flash.net.*; - import org.iotashan.oauth.*; import net.systemeD.halcyon.AttentionEvent; import net.systemeD.halcyon.MapEvent; @@ -95,55 +94,12 @@ package net.systemeD.halcyon.connection { private function mapLoadStatus(event:HTTPStatusEvent):void { } - protected var appID:OAuthConsumer; - protected var authToken:OAuthToken; - override public function setAuthToken(id:Object):void { - authToken = OAuthToken(id); - } - - override public function deleteAuthToken():void { - authToken = null; - var obj:SharedObject = SharedObject.getLocal("access_token","/"); - obj.setProperty("oauth_token", null); - obj.setProperty("oauth_token_secret", null); - try { obj.flush(); } catch (e:Error) {} - } - - override public function hasAccessToken():Boolean { - return !(getAccessToken() == null); - } - - override public function setAccessToken(key:String, secret:String):void { - if (key && secret) { - authToken = new OAuthToken(key, secret); - } - } - - /* Get the stored access token, or try setting it up from loader params */ - private function getAccessToken():OAuthToken { - if (authToken == null) { - var key:String = getParam("oauth_token", null); - var secret:String = getParam("oauth_token_secret", null); - - if ( key != null && secret != null ) { - authToken = new OAuthToken(key, secret); - } - } - return authToken; - } - - private function getConsumer():OAuthConsumer { - if (appID == null) { - var key:String = getParam("oauth_consumer_key", null); - var secret:String = getParam("oauth_consumer_secret", null); - - if ( key != null && secret != null ) { - appID = new OAuthConsumer(key, secret); - } - } - return appID; - } + // OAuth2 config + override public function setAccessToken(token:String):void { oauthAccessToken = token; } + override public function hasAccessToken():Boolean { return oauthAccessToken != null; } + override public function getAccessToken():String { return oauthAccessToken; } + override public function deleteAccessToken():void { oauthAccessToken = null; } // note you may need to blank the SharedObject too private var httpStatus:int = 0; @@ -207,21 +163,14 @@ package net.systemeD.halcyon.connection { dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't close changeset", 1)); } - private function signedOAuthURL(url:String, method:String):String { - // method should be PUT, GET, POST or DELETE - var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1(); - var oauthRequest:OAuthRequest = new OAuthRequest(method, url, null, getConsumer(), authToken); - var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING); - return String(urlStr); - } - private function sendOAuthPut(url:String, xml:XML, onComplete:Function, onError:Function, onStatus:Function):void { // build the request - var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "PUT")); + var urlReq:URLRequest = new URLRequest(url); urlReq.method = "POST"; if (xml) { urlReq.data = xml.toXMLString(); } else { urlReq.data = true; } urlReq.contentType = "application/xml"; - urlReq.requestHeaders = [ new URLRequestHeader("X-HTTP-Method-Override", "PUT"), + urlReq.requestHeaders = [ new URLRequestHeader("Authorization", "Bearer "+oauthAccessToken), + new URLRequestHeader("X-HTTP-Method-Override", "PUT"), new URLRequestHeader("X-Error-Format", "XML") ]; var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onComplete); @@ -231,8 +180,9 @@ package net.systemeD.halcyon.connection { } private function sendOAuthGet(url:String, onComplete:Function, onError:Function, onStatus:Function):void { - var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "GET")); + var urlReq:URLRequest = new URLRequest(url); urlReq.method = "GET"; + urlReq.requestHeaders = [ new URLRequestHeader("Authorization", "Bearer "+oauthAccessToken) ]; var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, onComplete); loader.addEventListener(IOErrorEvent.IO_ERROR, onError); @@ -266,9 +216,9 @@ package net.systemeD.halcyon.connection { // build the actual request var serv:HTTPService=new HTTPService(); serv.method="POST"; - serv.url=signedOAuthURL(url, "POST"); + serv.url=url; serv.contentType = "text/xml"; - serv.headers={'X-Error-Format':'xml'}; + serv.headers={'X-Error-Format':'xml', "Authorization": "Bearer "+oauthAccessToken}; serv.request=" "; serv.resultFormat="e4x"; serv.requestTimeout=0; diff --git a/net/systemeD/potlatch2/dialogs/OptionsDialog.mxml b/net/systemeD/potlatch2/dialogs/OptionsDialog.mxml index 57177fe..6b652ea 100644 --- a/net/systemeD/potlatch2/dialogs/OptionsDialog.mxml +++ b/net/systemeD/potlatch2/dialogs/OptionsDialog.mxml @@ -57,11 +57,11 @@ - - + + - + @@ -152,14 +152,14 @@ } private function doLogout():void { - conn.deleteAuthToken(); + conn.deleteAccessToken(); logout.enabled = false; } private function doReset():void { - conn.deleteAuthToken(); - userState.setProperty("oauth_consumer_key",null); - userState.setProperty("oauth_consumer_secret",null); + conn.deleteAccessToken(); + userState.setProperty("oauth2_client_id",null); + userState.setProperty("oauth2_client_secret",null); dataServer.text="https://www.openstreetmap.org/api/0.6/"; overviewServer.text="https://tile.openstreetmap.org/"; dataServerSet(); overviewServerSet(); @@ -173,13 +173,13 @@ private function overviewServerSet():void { userState.setProperty("overview_tiles",overviewServer.text); flush(); } - private function oauthKeySet():void { - userState.setProperty("oauth_consumer_key",oauthKey.text); flush(); - conn.deleteAuthToken(); + private function oauthIdSet():void { + userState.setProperty("oauth2_client_id",oauthId.text); flush(); + conn.deleteAccessToken(); } private function oauthSecretSet():void { - userState.setProperty("oauth_consumer_secret",oauthSecret.text); flush(); - conn.deleteAuthToken(); + userState.setProperty("oauth2_client_secret",oauthSecret.text); flush(); + conn.deleteAccessToken(); } ]]> diff --git a/net/systemeD/potlatch2/save/OAuthPanel.mxml b/net/systemeD/potlatch2/save/OAuthPanel.mxml index f6752d5..96cc71c 100644 --- a/net/systemeD/potlatch2/save/OAuthPanel.mxml +++ b/net/systemeD/potlatch2/save/OAuthPanel.mxml @@ -1,159 +1,113 @@ - + xmlns:s="library://ns.adobe.com/flex/spark" + title="Sign in to OpenStreetMap" + creationComplete="init()" + width="900" height="700" > - - - - - - - + + + + + + + + + + + + + -1) { - monitorTimeout = setTimeout(monitorForCallback, 100); + var oauth2:OAuth2 = new OAuth2( + getAuthorizeURL(), + getAccessTokenURL(), + LogSetupLevel.WARN); + var grant:IGrantType = new AuthorizationCodeGrant( + stageWebView, + getClientID(), + getClientSecret(), + getCallbackURL(), + "read_prefs write_prefs write_api read_gpx write_gpx write_notes openid"); + oauth2.addEventListener(GetAccessTokenEvent.TYPE, onGetAccessToken); + oauth2.getAccessToken(grant); + } + + private function onGetAccessToken(getAccessTokenEvent:GetAccessTokenEvent):void { + stageWebView.dispose(); + removeEventListener("render", onRender); + if (!getAccessTokenEvent.accessToken) { + connection.dispatchEvent(new AttentionEvent(AttentionEvent.ALERT, null, "Couldn't log in - check your username and password")); + return; + } + connection.setAccessToken(getAccessTokenEvent.accessToken); + // also has .tokenType (="Bearer"), .scope (=requested scope) + dispatchEvent(new Event(ACCESS_TOKEN_EVENT)); + PopUpManager.removePopUp(this); + } + + private function onRender(event:Event):void { + var rectangle:Rectangle = viewPort.getBounds(stage); + if (rectangle.width > 0 && rectangle.height > 0) { + stageWebView.viewPort = rectangle; } - // otherwise, if we're not on OSM nor the callback, give up } - - private function getResponseToken(loader:URLLoader):OAuthToken { - var vars:URLVariables = new URLVariables(loader.data); - - // build out request token - var token:OAuthToken = new OAuthToken( - String(vars["oauth_token"]), - String(vars["oauth_token_secret"])); - return token; - } - - private function getAccessToken():void { - tryAccessButton.enabled=false; - clearTimeout(monitorTimeout); - var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1(); - var consumer:OAuthConsumer = getConsumer(); - var oauthRequest:OAuthRequest = new OAuthRequest("GET", connection.oauthAccessToken, - null, consumer, requestToken); - var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING) - var urlReq:URLRequest = new URLRequest(String(urlStr)); - var loader:URLLoader = new URLLoader(); - loader.addEventListener(Event.COMPLETE, loadedAccessToken); - loader.addEventListener(IOErrorEvent.IO_ERROR, accessTokenError); - loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus); - loader.load(urlReq); - } - - private function loadedAccessToken(event:Event):void { - oauthHTML.htmlLoader.loadString("Successfully logged into OpenStreetMap"); - trace("Yay! response: "+URLLoader(event.target).data); - PopUpManager.removePopUp(this); - - _accessToken = getResponseToken(URLLoader(event.target)); - connection.setAuthToken(_accessToken); - dispatchEvent(new Event(ACCESS_TOKEN_EVENT)); - } - - public function get accessToken():OAuthToken { return _accessToken; } - public function get shouldRemember():Boolean { return rememberMe.selected; } - - private function cancelOAuth():void { + public function get shouldRemember():Boolean { return rememberMe.selected; } + private function cancelOAuth():void { + stageWebView.dispose(); + removeEventListener("render", onRender); PopUpManager.removePopUp(this); - clearTimeout(monitorTimeout); } - private function accessTokenError(event:IOErrorEvent):void { - tryAccessButton.enabled=false; - if ( lastHTTPStatus == 401 ) { - oauthHTML.htmlLoader.loadString("Sorry, access was denied. Please check and try again."); - } else { - oauthHTML.htmlLoader.loadString("Sorry, an error occurred ("+lastHTTPStatus+". Please try again."); - } - } - - private function getConsumer():OAuthConsumer { - var key:String = connection.getParam("oauth_consumer_key", ""); - var secret:String = connection.getParam("oauth_consumer_secret", ""); - return new OAuthConsumer(key, secret); - } + private function getAuthorizeURL():String { return connection.getParam("oauth2_authorize_url", ""); } + private function getAccessTokenURL():String { return connection.getParam("oauth2_access_token_url", ""); } + private function getCallbackURL():String { return connection.getParam("oauth2_callback_url", ""); } + private function getClientID():String { return connection.getParam("oauth2_client_id", ""); } + private function getClientSecret():String { return connection.getParam("oauth2_client_secret", ""); } ]]> - + diff --git a/net/systemeD/potlatch2/save/SaveManager.as b/net/systemeD/potlatch2/save/SaveManager.as index 9900d2a..16e5b7a 100644 --- a/net/systemeD/potlatch2/save/SaveManager.as +++ b/net/systemeD/potlatch2/save/SaveManager.as @@ -9,7 +9,6 @@ package net.systemeD.potlatch2.save { import mx.events.CloseEvent; import net.systemeD.halcyon.connection.*; import net.systemeD.potlatch2.controller.*; - import org.iotashan.oauth.*; public class SaveManager { @@ -64,11 +63,9 @@ package net.systemeD.potlatch2.save { oauthPanel.setConnection(_connection); var listener:Function = function(event:Event):void { - var accessToken:OAuthToken = oauthPanel.accessToken; if ( oauthPanel.shouldRemember ) { - var obj:SharedObject = SharedObject.getLocal("access_token","/"); - obj.setProperty("oauth_token", accessToken.key); - obj.setProperty("oauth_token_secret", accessToken.secret); + var obj:SharedObject = SharedObject.getLocal("oauth2_access_token","/"); + obj.setProperty("access_token", _connection.oauthAccessToken); try { obj.flush(); } catch (e:Error) {} } onCompletion(); diff --git a/potlatch2-app-cpu.xml b/potlatch2-app-cpu.xml index 0af40df..6868996 100644 --- a/potlatch2-app-cpu.xml +++ b/potlatch2-app-cpu.xml @@ -1,6 +1,6 @@ - + net.systemed.potlatch - 3.0 + 3.1 Potlatch resources/potlatch2.swf diff --git a/potlatch2-app-linux.xml b/potlatch2-app-linux.xml index e5cb15a..f0d6e6e 100644 --- a/potlatch2-app-linux.xml +++ b/potlatch2-app-linux.xml @@ -1,6 +1,6 @@ net.systemed.potlatch - 3.0 + 3.1 Potlatch resources/potlatch2.swf diff --git a/potlatch2-app.xml b/potlatch2-app.xml index 84b9afd..b397f5e 100644 --- a/potlatch2-app.xml +++ b/potlatch2-app.xml @@ -1,6 +1,6 @@ - + net.systemed.potlatch - 3.0 + 3.1 Potlatch resources/potlatch2.swf diff --git a/potlatch2.mxml b/potlatch2.mxml index 378719e..753a08a 100644 --- a/potlatch2.mxml +++ b/potlatch2.mxml @@ -146,7 +146,7 @@ private var mouseTimer:Timer; private var resizeTimer:Timer; - public var version:String="3.0"; + public var version:String="3.1"; include "build_date.as"; private function startInit():void { @@ -220,8 +220,11 @@ var params:Object = { api: "https://www.openstreetmap.org/api/0.6/", connection: "XML", - oauth_consumer_key: "8IJxvRqJ2b2Rgfv6RCf6Sw", - oauth_consumer_secret: "Ojod3JTQCPCPOQ3HZNlX5bxiRwLTtyzgcHCiTcyI", + oauth2_authorize_url: "https://www.openstreetmap.org/oauth2/authorize", + oauth2_access_token_url: "https://www.openstreetmap.org/oauth2/token", + oauth2_callback_url: "https://www.systemed.net/potlatch/oauth_callback", + oauth2_client_id: "__client_id_here__", + oauth2_client_secret: "__client_secret_here__", lat: 0, lon: 0, zoom: 2, @@ -338,8 +341,8 @@ floatingMap.y=36; // set the access token from saved cookie - var tokenObject:SharedObject = SharedObject.getLocal("access_token","/"); - conn.setAccessToken(tokenObject.data["oauth_token"], tokenObject.data["oauth_token_secret"]); + var tokenObject:SharedObject = SharedObject.getLocal("oauth2_access_token","/"); + conn.setAccessToken(tokenObject.data["access_token"]); // Load any requested GPX track if (loaderInfo.parameters['gpx']) {