Archive
Dropbox integration with Sitecore
So consider this an Alpha-release, I’ve used this Dropbox Context we build in the last blog post “introduction to the dropbox api“. I’ve changend it a little bit so I can be used to make a static .aspx file where you can build the accestoken file. The changend Dropbox context is shown below
public class DropboxContext { private OAuthToken _accessToken; private string _applicationKey; private string _applicationSecret; private string _accessTokenPath; private OAuthRestClient _apiRestClient; private OAuthRestClient _contentiRestClient; private string _version = "1.0"; public DropboxContext(DropBoxSettings settings) { _applicationKey = settings.ApplicationKey; _applicationSecret = settings.ApplicationSecret; _accessTokenPath = settings.AccessTokenPath; SetupApiRestClient(); SetupContentRestClient(); } private void SetupApiRestClient() { _apiRestClient = new OAuthRestClient("https://api.dropbox.com"); _apiRestClient.ConsumerKey = _applicationKey; _apiRestClient.ConsumerSecret = _applicationSecret; _apiRestClient.Version = _version; } private void SetupContentRestClient() { _contentiRestClient = new OAuthRestClient("https://api-content.dropbox.com"); _contentiRestClient.ConsumerKey = _applicationKey; _contentiRestClient.ConsumerSecret = _applicationSecret; _contentiRestClient.Version = _version; } public OAuthToken GetRequestToken() { return new OAuthToken(_apiRestClient.GetRequestToken("1/oauth/request_token")); } public void AuthorizeToken(OAuthToken token) { Process.Start("https://www.dropbox.com/1/oauth/authorize?oauth_token=" + token.Token); } public void SetAccesToken() { OAuthToken accesToken = GetAccesTokenFromStore(); if (accesToken == null) { OAuthToken requestToken = GetRequestToken(); AuthorizeToken(requestToken); // accesToken = UpgradeRequsetTokenToAccesToken(requestToken); } _accessToken = accesToken; } public void UpgradeRequsetTokenToAccesToken(OAuthToken requestToken) { if (requestToken == null) return; string tokenString = _apiRestClient.GetAccesToken("1/oauth/access_token", requestToken); OAuthToken token = new OAuthToken(tokenString); StoreAccessToken(tokenString); } public void StoreAccessToken(string tokenString) { FileStream fs = File.Open(_accessTokenPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); StreamWriter sw = new StreamWriter(fs); sw.WriteLine(tokenString); sw.Close(); fs.Close(); } private OAuthToken GetAccesTokenFromStore() { OAuthToken token = null; string tokenString = TokenStringFromFile(); if (!string.IsNullOrEmpty(tokenString)) token = new OAuthToken(tokenString); return token; } private string TokenStringFromFile() { FileStream fs = File.Open(_accessTokenPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); StreamReader sw = new StreamReader(fs); string tokenString = sw.ReadToEnd(); sw.Close(); fs.Close(); return tokenString; } public AccountInfo AccountInfo() { var response = _apiRestClient.QueryServer<AccountInfo>("1/account/info", GetAccesTokenFromStore()); return response; } public FileEntry GetMetadata(string path) { string requestPath = "1/metadata/dropbox"; if (!String.IsNullOrEmpty(path)) requestPath = String.Format("{0}{1}", requestPath, path.ToLower()); var response = _apiRestClient.QueryServer<FileEntry>(requestPath, GetAccesTokenFromStore()); return response; } public byte[] GetFile(string path) { var response = _contentiRestClient.GetStream("1/files/" + path, GetAccesTokenFromStore()); return response; } }
Since this i an early release I’ve build this simple .aspx page that provides the user with a link to allow acces to their dropbox and writes the accesToken to a flat file, the file can we then copy to the sitecore data folder for later use.
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> Token : <%# Token.Token %> <br /> Token Secret <%# Token.TokenSecret %><br /> <a href="https://www.dropbox.com/1/oauth/authorize?oauth_token=<%# Token.Token %>" target="_blank">Autherize Dropbox</a> <asp:Button ID="Button2" Text="Store Token" runat="server" onclick="Button2_Click" /> </div> </form> </body> </html>
public partial class AutherizeDropBox : System.Web.UI.Page { private OAuthToken token; private DropboxContext context; protected void Page_Load(object sender, EventArgs e) { context = new DropboxContext(SitecoreDropboxSettingsRepository.Get()); if (!IsPostBack) { DataBind(); } } protected void Page_PreRender(object sender, EventArgs e) { } protected OAuthToken Token { get { if (ViewState["token"] != null) { token = new OAuthToken(); token.Token = ViewState["token"].ToString(); token.TokenSecret = ViewState["tokenSecret"].ToString(); } if (token == null) { token = context.GetRequestToken(); ViewState["token"] = token.Token; ViewState["tokenSecret"] = token.TokenSecret; } return token; } } protected void Button2_Click(object sender, EventArgs e) { OAuthToken t = Token; context.UpgradeRequsetTokenToAccesToken(t); } }
For dropbox context to work we need a valid dropboxcontext setting datacontainer. Information for this i stored as settings in the web.config
<setting name="ApplicationKey" value="YOURAPPLIKATIONKEY" /> <setting name="ApplicationSecret" value="YOURAPPLIKATIONSECRET" /> <setting name="AccessTokenPath" value="PATHTOYOUFILETHATCONTAINSACCESSTOKEN" />
For this simple version of dropbox syncing I’ve build a simple sitecore task to run a “Download all service” from dropbox. It’s start by deleting all old the previously downloaded content from dropbox and the download from all content from dropbox root ie. “dropbox/”.
public class DropboxDownloadTask { CreateMediaItemService _createMediaItemService; public void Execute() { _createMediaItemService = new CreateMediaItemService(); GetRoot(); } private void GetRoot() { MetatadataRepository metatadataRepository = new MetatadataRepository(); FileEntry root = metatadataRepository.Get(); IterateOverChildren(root); } private void IterateOverChildren(FileEntry folderEntry) { foreach (FileEntry entry in folderEntry.Contents) { if (entry.Is_Dir) IterateOverChildren(GetFileEntry(entry)); else _createMediaItemService.CreateMediaItem(entry); } } private FileEntry GetFileEntry(FileEntry entry) { MetatadataRepository metatadataRepository = new MetatadataRepository(); return metatadataRepository.Get(entry); } }
The task uses some file- and metadata-repositories that communicate with dropboxcontext and a simple settings repository.
public static class SitecoreDropboxSettingsRepository { public static DropBoxSettings Get() { DropBoxSettings settings = new DropBoxSettings(); settings.ApplicationKey = Settings.GetSetting("ApplicationKey"); settings.ApplicationSecret = Settings.GetSetting("ApplicationSecret"); string path = Settings.GetSetting("AccessTokenPath"); settings.AccessTokenPath = HttpContext.Current.Server.MapPath(path); return settings; } }
public class MetatadataRepository { private DropboxContext _context; public MetatadataRepository() { _context = new DropboxContext(SitecoreDropboxSettingsRepository.Get()); } public FileEntry Get() { return _context.GetMetadata(String.Empty); } public FileEntry Get(FileEntry entry) { return _context.GetMetadata(entry.Path); } }
public class FileDataRepository { private DropboxContext _context; public FileDataRepository() { _context = new DropboxContext(SitecoreDropboxSettingsRepository.Get()); } public byte[] Get(FileEntry entry) { return _context.GetFile(entry.Root+entry.Path); } }
Finally the task have a simple service that is responsible for adding a media stream to the sitecore media library.
public class CreateMediaItemService { private MediaCreator _creator; private MediaCreatorOptions _mediaOptions; public void CreateMediaItem(FileEntry folderName) { using (new SecurityDisabler()) AddStreamToMediaLibrary(folderName); } private void AddStreamToMediaLibrary(FileEntry folderName) { if(folderName.Parent == "dropbox") SetMediaOptions(folderName.Parent, folderName.Name); else SetMediaOptions(String.Format("dropbox/{0}",folderName.Parent), folderName.Name); _creator = new MediaCreator(); FileDataRepository fileDataRepository = new FileDataRepository(); byte[] bytes = fileDataRepository.Get(folderName); MemoryStream theMemStream = new MemoryStream(); theMemStream.Write(bytes, 0, bytes.Length); try { _creator.CreateFromStream(theMemStream, folderName.Name, _mediaOptions); } catch (Exception e) { throw new Exception(folderName.Name, e); } } private void SetMediaOptions(string sitecorePath, string itemName) { _mediaOptions = new MediaCreatorOptions(); _mediaOptions.FileBased = false; _mediaOptions.IncludeExtensionInItemName = false; _mediaOptions.KeepExisting = false; _mediaOptions.Versioned = false; _mediaOptions.Destination = String.Format("/sitecore/media library/{0}/{1}", sitecorePath, ItemUtil.ProposeValidItemName(itemName)); _mediaOptions.Database = Database.GetDatabase("master"); } }
Offcourse there are room for improvement and the code could use some cleaning up to is more “Clean Code” Uncle Bob. You can download the source code for this project at Pentia. I’m more then interrested in hearing what features should be on the todo-list so let me know.
Introduction to the dropbox api version 1.0
In this post i will explore some of the basic functionality of the new Dropbox Api, you can read more about here https://www.dropbox.com/developers/reference/api. As you can see in the description you talk to dropbox using rest, and OAuth as authentication method. I could of course buil my own OAuth helper instead i will use this OAuth base utility found at https://www.dropbox.com/developers/reference/api not that i that think it written as clean “clean code” I like it does the job, but i will give some trouble writting clean crisp method of some of the classes in this post. As a restclient I will use the client from http://restsharp.org/.
Some of the code in this post is also done very naive with no error handling so of course if you want to use the code one should deffently address this issue.
Before you start coding anything you first need to go to dropbox and create an app
https://www.dropbox.com/developers/apps
The app key and app secret should be replaced where necessary through out this post.
The authentication process at dropbox consist of three steps
- Get a request token
- Authorize the request token from step 1
- Upgrade the access token from step 1 to an Access token. The Access token should nt be stored and can be used in all future interaction with dropbox.
First a Token from dropbox i delivered from a rest response in form given below
oauth_token_secret=b9q1n5il4lcc&oauth_token=mh7an9dkrg59
So i made this simple yet NOT VERY CLEAN CODE CLASS
public class OAuthToken { public OAuthToken() { } public OAuthToken(string tokenString) { string[] urlParts = tokenString.Split('&'); string[] secretParts = urlParts[0].Split('='); string[] tokenParts = urlParts[1].Split('='); TokenSecret = secretParts[1]; Token = tokenParts[1]; } public string TokenSecret { get; set; } public string Token { get; set; } }
Most of the other restquest a response with Json and the restsharp can deserialize them in datacontainers.
Now with token class in place we can create the firs rest call to supply us with at instance of a request token.
We first need some dropbox OAuth settings stored in this datacontainer
public class DropBoxSettings { public string ApplicationKey { get; set; } public string ApplicationSecret { get; set; } public string AccessTokenPath { get; set; } }
With this we can create a new OAuthrestClient
public class OAuthRestClient { private OAuthBase _oAuth; private RestClient _restClient; public OAuthRestClient(string restHost) { _oAuth = new OAuthBase(); _restClient = new RestClient(restHost); } public string GetRequestToken(string resource) { RestRequest request = AddOAuthParameters(resource); RestResponse response = _restClient.Execute(request); return response.Content; } public string GetAccesToken(string resource,OAuthToken token) { RestRequest request = AddOAuthParameters(resource,token); RestResponse response = _restClient.Execute(request); return response.Content; } public T QueryServer<T>(string resource, OAuthToken token) where T : new() { RestRequest request = AddOAuthParameters(resource, token); RestResponse<T> restResponse = _restClient.Execute<T>(request); return restResponse.Data; } public byte[] GetStream(string resource, OAuthToken token) { RestRequest request = AddOAuthParameters(resource, token,false); RestResponse restResponse = _restClient.Execute(request); return restResponse.RawBytes; } private RestRequest AddOAuthParameters(string resource, OAuthToken token=null,bool encodeSignatur=true) { RestRequest request = new RestRequest(Method.GET); string nonce = _oAuth.GenerateNonce(); string timeStamp = _oAuth.GenerateTimeStamp(); request.Resource = resource; request.AddParameter("oauth_consumer_key", ConsumerKey); request.AddParameter("oauth_nonce", nonce); request.AddParameter("oauth_signature_method", "HMAC-SHA1"); request.AddParameter("oauth_timestamp", timeStamp); if(token != null) request.AddParameter("oauth_token", token.Token); request.AddParameter("oauth_version", "1.0"); request.AddParameter("oauth_signature", BuildSignatureWithToken(resource, nonce, timeStamp, token, encodeSignatur)); return request; } private string BuildSignatureWithToken(string resource, string nonce, string timeStamp, OAuthToken token,bool encodeSignature) { string normalizedUrl; string normalizedRequestParameters; string sig; if(token == null) sig = _oAuth.GenerateSignature(new Uri(string.Format("{0}/{1}", _restClient.BaseUrl, resource)), ConsumerKey, ConsumerSecret, "", "", "GET", timeStamp, nonce, out normalizedUrl, out normalizedRequestParameters); else sig = _oAuth.GenerateSignature(new Uri(string.Format("{0}/{1}", _restClient.BaseUrl,resource)),ConsumerKey, ConsumerSecret,token.Token, token.TokenSecret,"GET", timeStamp, nonce, out normalizedUrl, out normalizedRequestParameters); if(encodeSignature) sig = HttpUtility.UrlEncode(sig); return sig; } public string Version { get; set; } public string ConsumerKey { get; set; } public string ConsumerSecret { get; set; } }
There are som input parameters that are bool which i due some restriction bugs in the OAuth base where a token not should be added in the requesttoken call. And the dropbox api where the signature shouldn’t be urlencode when querying api-content.dropbox.com. We can now use this to get a request token.
public OAuthToken GetRequestToken() { OAuthRestClient apiRestClient = new OAuthRestClient("https://api.dropbox.com"); apiRestClient.ConsumerKey = YOUR APP KEY; apiRestClient.ConsumerSecret = YOUR APP SECRET; apiRestClient.Version = "1.0"; return new OAuthToken(_apiRestClient.GetRequestToken("1/oauth/request_token")); }
Now with request token we can prompt the user for access to his or hers dropbox account this can for now only be done in a browser
private void AuthorizeToken(OAuthToken token) { Process.Start("https://www.dropbox.com/1/oauth/authorize?oauth_token=" + token.Token); }
Now with application authorized we ca upgrade the requesttoken to an accestoken, and store it in a secure place for now we store it as a string in a plaintext file.
private OAuthToken UpgradeRequsetTokenToAccesToken(OAuthToken requestToken) { OAuthRestClient apiRestClient = new OAuthRestClient("https://api.dropbox.com"); apiRestClient.ConsumerKey = YOUR APP KEY; apiRestClient.ConsumerSecret = YOUR APP SECRET; apiRestClient.Version = "1.0"; string tokenString = apiRestClient.GetAccesToken("1/oauth/access_token", requestToken); OAuthToken token = new OAuthToken(tokenString); StoreAccessToken(tokenString); return token; } private void StoreAccessToken(string tokenString) { FileStream fs = File.Open(_accessTokenPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); StreamWriter sw = new StreamWriter(fs); sw.WriteLine(tokenString ); sw.Close(); fs.Close(); } private OAuthToken GetAccesTokenFromStore() { OAuthToken token = null; string tokenString = TokenStringFromFile(); if(!string.IsNullOrEmpty(tokenString)) token = new OAuthToken(tokenString); return token; } private string TokenStringFromFile() { FileStream fs = File.Open(_accessTokenPath, FileMode.OpenOrCreate, FileAccess.ReadWrite); StreamReader sw = new StreamReader(fs); string tokenString = sw.ReadToEnd(); sw.Close(); fs.Close(); return tokenString; }
Now with an accestoken we can start talking to dropbox to the more fun stuff for example the userinformation
public class AccountInfo { public string Uid { get; set; } public string Display_Name { get; set; } public string Country { get; set; } public QuotaInfo Quota_Info { get; set; } }
public class QuotaInfo { public string Shared { get; set; } public string Quota { get; set; } public string Normal { get; set; } }
or metadata for a a given path in the users dropbox
public class FileEntry { public string Size { get; set; } public string Rev { get; set; } public string Thumb_Exists { get; set; } public string Bytes { get; set; } public string Modified { get; set; } public string Path { get; set; } public bool Is_Dir { get; set; } public string Icon { get; set; } public string Root { get; set; } public string Mime_Type { get; set; } public string Revision { get; set; } public List<FileEntry> Contents { get; set; } public string Name { get { if (String.IsNullOrEmpty(Path)) return String.Empty; if (Path == "/") return "dropbox"; return Path.Substring(Path.LastIndexOf("/") + 1); } } public string Parent { get { if (String.IsNullOrEmpty(Path)) return String.Empty; if (Path.LastIndexOf("/") == 0) return "dropbox"; return Path.Substring(0,Path.LastIndexOf("/")); } } }
public AccountInfo AccountInfo() { OAuthRestClient apiRestClient = new OAuthRestClient("https://api.dropbox.com"); apiRestClient.ConsumerKey = YOUR APP KEY; apiRestClient.ConsumerSecret = YOUR APP SECRET; apiRestClient.Version = "1.0"; var response = apiRestClient.QueryServer<AccountInfo>("1/account/info", YOUR ACCES TOKEN); return response; }</pre> <pre>
Now we could can the byte[] for a dropbox entry if it is not a folder, note when calling the api-content.dropbox.com the SIGNATURE PASSED TO DROPBOX SHOULD NOT BE URL -ENCRYPTED why i have no clue
public byte[] GetFile(string path) { OAuthRestClient apiRestClient = new OAuthRestClient("https://api-content.dropbox.com"); apiRestClient.ConsumerKey = YOUR APP KEY; apiRestClient.ConsumerSecret = YOUR APP SECRET; apiRestClient.Version = "1.0"; var response = apiRestclient.GetStream("1/files/dropbox/" + HttpUtility.UrlEncode(path), YOURRACCESTOKEN); return response; }
I’ve gathered all the functions in a simple Dropbox context.
So this wraps it up for now. in future post I will look into uploading files, but in the next post I will integrate this into the sitecore media library, for now only with download only and always overwrite. Coming soon so stay tuned.