random image random image random image random image random image random image

With some of the recent changes to the Services module in Drupal and without a great Flex integration example I was pressed to work my way through putting the pieces together. There are some good starter examples but I hope to take this start to finish including the use of api keys and session ids in amfphp service calls. I used this example as my starting point: A really, really simple Services + AMFPHP example using Flex, but I do repeat many of these steps in this tutorial as well.

Some assumptions:

  • Drupal 6 installed
  • Views module installed
  • Services module installed
  • AMFPHP module installed – with AMFPHP 1.9 Beta 2 – this needs to be in the amfphp module directory

Starting with setting up Drupal, we are going to enable the services module, amfphp module, and for this example node service, system service, user service, and views service. After enabled go to the site building/services and make sure that in settings “use keys” and “use sessid” is checked (this is done by default). Now go to the keys tab and create a key with the title of the site you are wanting the key to be used for (just a label for your purposes) and the domain which it will reside on (this will be checked by the service call). This process will produce a key we will use later in our flex application. Note this or have this page available.

Now to the Flex side. Create a new project and include in the src directory the services-config.xml that comes with amfphp (in the browser directory). Modify the uri in this line to point to your amfphp service:

<endpoint uri="http://flashservices/gateway.php" class="flex.messaging.endpoints.AMFEndpoint"/>

<endpoint uri="/services/amfphp" class="flex.messaging.endpoints.AMFEndpoint"/>

This may be different if you have your drupal install inside a directory and not at the root (i.e. /subdir/services/amfphp)

In your project properties/compiler arguments append this line to the existing arguments:

-locale en_US -services “services-config.xml”

We also want to grab the as3corlib for its crypto functions. We’ll use this to hash our api key before passing it to Druapl. Include this in your libs directory.

Now we need to add some remote objects to our Flex project and for this tutorial a couple of forms and a data grid. In this example we will create a new user, automatically log them in, create some node data and view that node data in the data grid (what you do with the information is up to you, this is simply to display different functionality).

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:Script source="ServicesTutorial.as"/>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="system" id="system">
<mx:method name="connect" result="onSystemConnect(event)"  fault="onFault(event)" />
</mx:RemoteObject>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="node" id="node">
<mx:method name="save" result="onNodeSaved(event)" fault="onFault(event)"/>
</mx:RemoteObject>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="user" id="user">
<mx:method name="save" result="onUserSaved(event)" fault="onFault(event)"/>
<mx:method name="login" result="onUserLogin(event)" fault="onFault(event)"/>
</mx:RemoteObject>
<mx:RemoteObject showBusyCursor="true" destination="amfphp" source="views" id="views">
<mx:method name="get" result="onViewsResult(event)"  fault="onFault(event)" />
</mx:RemoteObject>

<mx:Form>
<mx:FormItem label="Username">
<mx:TextInput id="usernameInput"/>
</mx:FormItem>
<mx:FormItem label="Password">
<mx:TextInput id="passwordInput" displayAsPassword="true"/>
</mx:FormItem>
<mx:FormItem label="Email">
<mx:TextInput id="emailInput"/>
</mx:FormItem>
<mx:FormItem >
<mx:Button label="Register" click="registerUser();"/>
</mx:FormItem>
</mx:Form>

<mx:Form>
<mx:FormItem label="Title">
<mx:TextInput id="titleInput" text="{selectedStories.selectedItem.node_title}"/>
</mx:FormItem>
<mx:FormItem label="Body">
<mx:TextArea id="bodyInput" text="{selectedStories.selectedItem.node_revisions_body}"/>
</mx:FormItem>
<mx:FormItem>
<mx:Button label="Submit" click="saveStory();"/>
</mx:FormItem>
</mx:Form>

<mx:DataGrid id="selectedStories" dataProvider="{theStories}">
<mx:columns>
<mx:DataGridColumn headerText="NID" dataField="nid"/>
<mx:DataGridColumn headerText="Title" dataField="node_title"/>
<mx:DataGridColumn headerText="Body" dataField="node_revisions_body"/>
</mx:columns>
</mx:DataGrid>

</mx:Application>

For the remote object the source property is the type of service (node, system, user, views), the destination matches the the destination ID in the services-config.xml, the method name is the service method we want to call (get, save, etc).

These remote objects need their corresponding result handlers so we will create another ActionScript file to hold our script. In this we will create all of the result handlers for our service calls. This including the single fault handler which would ideally would have different messages/actions for each type of service call.

import com.adobe.crypto.HMAC;
import com.adobe.crypto.SHA256;

import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ArrayUtil;

private var apiKey:String = "your api key from drupal services";
private var apiDomain:String = "your api domain from drupal services";

public function onSystemConnect(event:ResultEvent):void{
}

public function onUserSaved(event:ResultEvent):void{
}

public function onUserLogin(event:ResultEvent):void{
}

public function onNodeSaved(event:ResultEvent):void{
}

public function onViewsResult(event:ResultEvent):void{
}

public function onFault(event:FaultEvent):void{
Alert.show("there was a problem");
}

public function hashKey(serviceMethod:String):Array{
var captureTime:String = (Math.round((new Date().getTime())/1000)).toString();
var captureRandom:String = randomString(10);
var hashString:String = captureTime + ";";
hashString += apiDomain + ";";
hashString += captureRandom +";";
hashString += serviceMethod;
return [HMAC.hash(apiKey,hashString,SHA256),apiDomain,captureTime,captureRandom];
}

private function randomString(Stringlength:Number):String{
var allowedChar:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var allowedArray:Array = allowedChar.split("");
var randomChars:String = "";
for (var i:Number = 0; i < Stringlength; i++){
randomChars += allowedArray[Math.floor(Math.random() * allowedArray.length)];
}
return randomChars;
}

At the end of this script is the hashing function which will return an array of the information we need and a helper random string generator. You’ll notice also that there are imports for the crypto functions in the as3corelib and we have set strings containing our api key and the api domain, fill these in with the information you generated with the services module.

Now we can start calling our services and telling flex what to do with the results. First we will connect to the system to get a session ID from drupal as well as check if there is a user currently logged in. We will call the system.connect method from an init function run in creation complete. Add the creationComplete event to the application and the corresponding function.

mxml:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init();">

as:
public function init():void{
system.connect();
}

When we get a response from system.connect it returns an object with the session id and a logged in user. We want to retreive this information and store it for subsequent calls. In the onSystemConnect method we’ll handle the results and store these in appropriate varaibles, we’ll put these at the top to keep them in scope with the rest of the functions.

private var sessionID:String;
private var currentUser:String;

public function onSystemConnect(event:ResultEvent):void{
sessionID = event.result.sessid;
currentUser = event.result.user.name;
}

Also for this call to work as an anonymous user we need to open up the permissions allowing anonymous users to connect to services (user/permissions/service module/access services). You’ll notice that I am storing the user’s name rather than uid. This is because Flex uses the uid internally as a “unique identifier.” When we save the user name with other data Drupal will complete the Drupal uid when it sees the name variable.

After we connect to services we want to do something. I have chosen to give the option to register a new user in Drupal. This next call is the user.save service. We are using the hashKey function and using the returned array as our first arguments for the user.save. We are passing the “user.save” string to the hashKey function because we are calling the system.connect method. The session id we retrieved from system.connect will be passed in and a username, password and email which are also required fields for a new user. I have created a new function to handle the registration data called registerUser. You may want some validation on these fields but I am omitting that for length.

public function registerUser():void{
var hashedArray:Array = hashKey("user.save");
var userCreate:Object = new Object();
userCreate.name = usernameInput.text;
userCreate.pass = passwordInput.text;
userCreate.mail = emailInput.text;
userCreate.status = 1;
user.save(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, userCreate);
}

I have also set the status on the userCreate so that the user is immediatly set to active. If this is not done admin approval would be required. And like system.connect for anonnymous users to create a new users, permissions will need to be changed (user_service module/create new users). I am choosing to log the user in right after registration but this could be done in a seperate action. Additional requirements for login are the username and password.

public function onUserSaved(event:ResultEvent):void{
var hashedArray:Array = hashKey("user.login");
user.login(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, usernameInput.text, passwordInput.text);
}

After we log in I’m going to grab all of the stories that have been created with a view that I have created. This view retrieves the body, title and nid fields from stories.

[Bindable]
private var theStories:Array;

public function onUserLogin(event:ResultEvent):void{
var hashedArray:Array = hashKey("views.get");
views.get(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, "getUserStories");
}

public function onViewsResult(event:ResultEvent):void{
theStories = ArrayUtil.toArray(event.result);
}

Again make sure that you have permissions to access views in the user permissions. Make sure the array we are populating is bindable because this array will populate our datagrid. You should now be able to register a new user, auto login and see the all stories in the data grid. If you wanted to log in an existing user you would have to set up another action to do just the login. The last thing we will do with services is change the content of one of the stories or create new stories by doing a node.save. Using the views that pulled in we can modify that information and send it back to Drupal.

public function saveStory():void{
var edit:Object;
if(selectedStories.selectedItem){
edit = selectedStories.selectedItem;
}
else{
edit = new Object;
}
edit.type = "story";
edit.title = titleInput.text;
edit.body = bodyInput.text;
var hashedArray:Array = hashKey("node.save");
node.save(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, edit);
}

public function onNodeSaved(event:ResultEvent):void{
var hashedArray:Array = hashKey("views.get");
views.get(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, "getUserStories");
}

If there is no story selected a new one will be created and saved to the view. After the node is saved it updates the views list. Give all appropriate permissions for these actions (create and edit).

I hope that this is starting point for expanding and using api keys/sessid in service calls from Flex to Drupal. Comments are welcome and I have included a full copy of the mxml and the as files below.

Source zip

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • MySpace
  • NewsVine
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • TwitThis
  • Diigo
  • FriendFeed
  • Identi.ca
  • Posterous
  • Suggest to Techmeme via Twitter
  • Twitter

36 Responses to “Api keys to the city (setting up a Flex 3 project with Drupal Services)”

  1. lgammo

    I could be wrong but I think this will work only on Drupal 6 version of Services (6.x-0.13) because the user.save method does not exist in Services 5.x-0.92.

  2. Erik

    Thanks for the comment, I have changed the assumptions above to include Drupal 6.

  3. csy

    hello !
    can you share your project (Flex Builder)?
    I have tried whole day,but achived several error . thanks

  4. Casey

    A quick note for new node creation be sure to set the creation time of the node:

    var my_date:Date = new Date();
    nodeObject.created = my_date.getTime();

    and for editing existing nodes, be sure to set the changed time of the node:

    var my_date:Date = new Date();
    nodeObject.changed = my_date.getTime()

    drupal 6 will complain if these arguments are not supplied. I used a simple if statement to handle both:

    public function nodeSave():void{

    var nodeObject:Object = new Object();
    var my_date:Date = new Date();
    nodeObject.type = “page”;
    nodeObject.nid = nid.text; // set to zero to create new node
    nodeObject.title = title.text;
    nodeObject.body = body.text;
    if (nodeObject.nid == 0) {
    nodeObject.created = my_date.getTime();
    } else {
    nodeObject.changed = my_date.getTime();
    }

    node.save(nodeObject);

    }

    If the NID is 0, then a new node will be created. Hope this helps, I didn’t know where else to post this.

    -Casey

  5. Erik

    csy, the project files are zipped at the end of the post. These three files are the only ones needed on the Flex side.

  6. YogiFish

    Nice example, but there are a few errors.

    Should have added this to the import section of the actionscript file.

    import mx.utils.ArrayUtil;

    I’m also receiving errors when attempting to save, but I can’t figure out the problem.
    Any help with a complete example of you configuration would be appreciate.

    Thanks,
    YogiFish

  7. Erik

    YogiFish, The link at the bottom with the source has been updated with a full project export, you should be able to import the zip into Flex builder. I have updated the post to reflect the missing import call. Thanks.

  8. Robbert

    When trying to register i get the message ‘the was a problem’.
    Debugging the projects gives an error:
    ReferenceError: Error #1069: Property sessid not found on Boolean and there is no default value.
    at ServicesTutorial/onSystemConnect()[C:\Users\FSC 2530\Documents\Flex Builder 3\ServicesTutorial\src\ServicesTutorial.as:21]

  9. Robbert

    It’s working now, i overlooked the _service settings in /admin/user/permissions
    But you have to logon first via /admin before the drupalflex connection works, else access denied

  10. Erik

    Robbert, Yes you do have to open up permissions for anonymous users to access services. If these permissions are correct you should be able to register a new user as an anonymous user.

  11. Robbert

    Yes it is working. Thank you for this great example. This is the first example that works.

  12. David

    Hi!
    This seems to be great. :) But I can’t get it to function properly… I open the project in Flex, update my APIkey & domain, then export as swf, put that swf on my server and launch it. when i try to register a user i get a “there was a problem” error msg. I have ALL the permissions allowed to everybody, so I don’t know where I’m going wrong. PLEASE help…
    thanks

  13. Michael Caudy

    Thanks for this example. I have an unusual problem: I cannot get Flex Builder to take the mxml app that you provided and create the corresponding html and swf files in the bin-debug folder. I can set the ServicesTutorial.mxml file to “Default Application” OK, but the html and swf files are not created.

    I have been using services successfully with D5. In one previous case, I saw the same problem: unable to create the html and swf files. The problem in that case was that the template files in the html-template directory were corrupted, since when I replaced those template files with working ones the proper html and swf files were then created in the bin-debug directory. However, replacing the template files in the ServicesTutorial app does not solve the problem in this case.

    Please advise. Thanks,

  14. sitron

    thanks for the source.
    when i try to use it, my user is created, but only the nid is set. (if i look in the db, name, password, email are blank, but it gets a unique id). so obviously the login part fails. (wrong username or password)
    if i check in my http debugger i see that the correct values are passed. (at register time: email, name, pass are all populated).
    is there anything to set up in service.module for this example to work?

  15. John

    You, sir, are a hero. Thanks for this.

  16. Erik

    David,
    If you place the swf anywhere but at the root of your server you may need to modify the services-config.xml to show that path. ie. swf is at /drupalinstall/ServicesTutorial.swf then line 20 in the services-config.xml should be “/drupalinstall/services/amfphp”. Also, in this example if you try to register more than one user without logging out it will throw an error.

  17. Erik

    Michael Caudy,
    I’m not sure about the trouble you are having with Flex Builder. You should just have to go to File->Import->Flex Project and select the zip provided. This will create the new self-contained project.

  18. Erik

    sitron,
    In this example the register and login are all rolled into one process. When you register in the application it also logs you in, so the information being passed that you see at register is also used to log in. You can modify the code to change this behavior if this is not the desired result.

  19. Brad

    I’m able to log-in a user, create a new user, and query the existing story nodes. But when I attempt to save a node using the code in your example I keep getting “Missing required arguments”. Is it necessary to include a ‘timestamp’ and ‘nonce’ argument with each save? I tried doing this but still am having trouble…perhaps I’m not doing it correctly.

    Thanks for putting this tutorial together…it’s a huge help!

  20. Brad

    I found my mistake. I was using the code on the web page, and not the zip file. The example code on the web page lists node.save(edit) The zip file corrects this with the appropriate arguments for the node.save call.

  21. Erik

    Brad,
    When you edit a node versus creating a new one, you do need to pass the nid of the node along with an updated “changed” date. All of the hash information is required with session and api keys enabled. Also thanks for the note about the tutorial code being different from the source download, I have updated that line.

  22. Using the Drupal services in Flex | by Hans Van de Velde

    [...] http://electricpineapple.net/?p=32 [...]

  23. Brad

    Thanks Erik, with your help I was able to get things working. I have one other question. I created a custom view with an argument for the node id. What’s the appropriate syntax for passing that argument to view.get? I added the value in an Array after the name of the view, but I get null objects as the return value. Here’s my code:

    views.get(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, “performanceObject_view”, ["3"]);

    “3″ is the node id argument that I’d like to pass to the view. Do you know what I’m doing wrong?

    Thanks again for the help.

  24. Erik

    Brad,
    The argument after the view name is an array of fields to be returned. You need to set this argument to null and pass your “3″ into the second slot after the view name:
    views.get(hashedArray[0], hashedArray[1], hashedArray[2], hashedArray[3], sessionID, “performanceObject_view”,null, ["3"]);

  25. Jeffer

    First , thaks for the example is really coll, but i have a problem, i import the sorce code and configure all. but in the registry an error ocurs:

    error: duplicate entry

    coul you help me please

    thank you

  26. Erik

    Jeffer,
    My guess would be that you are trying to register a user that is already in the system. This example has a few short cuts that may not be applicable for your situation, you may need to separate the registration from the log in.

  27. Paul Johnson

    I am suffering from “Authentication failed: Access denied” error when I try and browse the views service on a views.get call. I am using “use keys” and sessionids.

    All the other services are working fine and your code is generating nodes happily.

    Has anyone else experienced problems and got a solution.

    I have granted annonymous and authenticated user all privileges.

  28. Martijn

    Hi Paul, i get the same problem. I get a access denied.
    Also tried this tutorial: http://www.novio.be/blog/?p=1034.
    there i get the same error when pulling up a view :s

    not found a solution yet, I’ve already set all permissions open for Anonymous users but the error still keeps coming…

  29. Jay

    Erik – thanks for this great example project – finally one that works! Couple Q’s:

    1. the default Token expiry time is 30s – why so short? what’s the risk in making this effectively infinite?
    2. how secure would you say drupal services are with keys and sessionIDs enabled, as your demo project implements it – would you implement a credit card processing application with it?

    Cheers!Jay

  30. Erik

    Jay,
    I did not set up the expiry time, just used default settings and I am not using this for high security purposes

    Vanitha,
    The last parameter in the node.save is the object to be saved, you may be able to add the tid and vid to this object before the save and get them to stick.

  31. Jomesili

    @ Casey the node.created = myDate.getTime() still returns errors on my Drupal side, I am using Drupal six, i am sure others have this problem too

  32. Paul

    TypeError: Error #1010: A term is undefined and has no properties.
    at com.adobe.crypto::HMAC$/hashBytes()[.../src/com/adobe/crypto/HMAC.as:114]

    My project is failing in the hashKey method before the call is even made… within the adobe library…?

  33. Micah Waldman

    Erik,

    Great post. Thank you so much. It provided me everything I needed to get going.

    Since this is the most complete “documentation” that I found on using Services from Flex, I wanted to add a couple of comments that I suspect would help others. Perhaps you want to mention them in your post itself:

    - Apparently ‘logout’ is a reserved word in Flex so you cannot do something like user.logout() – you get compiler errors. Instead you need to do user.getOperation(‘logout’).send(). It took me a while to find this out (see http://drupal.org/node/178051#comment-919923).

    - When using the latest Services module views.get method, there are two options to get the view data:
    1. If you set the Style to Unformatted and then Row style to Fields, then you get back from views.get the fields that are defined in the view. However, there are some limitations here – for example, fields with grouped multiple values are not returned. This has to do with how Services integrates with Views (see more at http://drupal.org/node/678202).
    2. You can set the Style to Unformatted and Row style to Node. This in principle returns the whole node, so you can access any fields you need. I say in principle because it was recently changed to return just the nid instead of the entire node object and there’s currently a discussion on how to handle this issue (see more at http://drupal.org/node/654644). I applied the patch listed in #28 of the above issue and it now returns the whole node again, which I need. Hopefully this will be resolved soon.

    Cheers,
    Micah

  34. Rocko

    Is this still the preferred method of working with drupal and the release of Flash Builder 4? Should the same technique still be used for flash builder4 interaction with a drupal based cms backend?

  35. Rocko

    I know that Flash builder 4 is now integrated with ZendAMF, so what is the step by step process for getting flash builder 4 to work with drupal?

  36. Erik

    Services is still kicking in Drupal and is a viable method for connecting with Flash builder, there may be different methods with the newer build but the same process should hold.

Leave a Reply

Powered by WordPress. Theme developed with WordPress Theme Generator. Header images provided by idee.
Copyright © Electric Pineapple. All rights reserved.