Feeds:
Posts
Comments

Posts Tagged ‘flex’

UPDATE

I put this at the top because I wanted to change the content of the below post. Having worked on this code for a few days now, I became aware of several short-falls in my assumptions.

1) You cannot extend skins. Which makes sense, a host component can have many skins which have many states. Extending a skin would add confusion as to where the states are defined for that “look and feel”.

2) Enabling drag and drop (while a UI interface function) on custom components through their skins is asking to violate the DRY principle. Even though the skin is now the top most layer of the <code>DragEvent</code> having different skins breaks this model.

Unfortunately the drag and drop examples from adobe for flex 4 still show mx components, but the differences page seems to lean towards putting drag and drop functionality in the spark component, not the skin class for the spark component.

ORIGINAL POST

Another upgrade bit of confusion that has since been squared away.

In the original application, mx:Panels were dragged around on a mx:Canvas; something similar to the Adobe Documentation examples

Both of these files were getting quite large as they handled look and feel, interaction of the components, and all data. Now with flex 4.0, one priority was to convert these two custom components to s:SkinnableContainer with respective s:Skin classes.

This has cut our file sizes 20-50 percent as we try to follow a more MVC architecture. However our drag-and-drop functionality ceased to work for these custom components.

Long story short, drag events were now showing the s:Skin class as the dragInitiator for a DragEvent instead of the (in my mind) expected host component of the skin.

This make sense from the point that the added skin is now the ‘top-most’ layer of the component, but the question of where to put the custom drag management code naturally presented itself.

Does it belong in the base “data” class (Component), or does it belong in the “skin” class (ComponentSkin). Adding to the confusion is that both classes accept dragEnter and dragDrop events (in addition to all other DragEvents.

Knowing that the outmost layer of the component is the skin, and attempting to adhere to the MVC architecture, the decision is made to include all drag (ie visual) effects in the s:Skin class.

Three links that helped me with this endeavor:
saturnboy drag-and-drop
evtimmy drag-and-drop skinning
From above:
Adobe drag and drop examples

Advertisements

Read Full Post »

Converting over from 3.5 to 4.0 has been a blast. Lots of new features have been introduced. However I started to see this error every now and again:

Error: Child elements of 'Group' serving as the default property value for 'mxmlContent' must be contiguous.

I would be able to get rid of it undoing whatever I had recently changed, but I pushed it to the back of my mind until now. I can replicate this error by creating a group that contains a layout tag after any visible children.

Over zealous copy/pasting a rectangle with a stroke into a group tag, bumping the layout tag lower caused this error for me.

Flex, trying to add visible elements and not knowing how to layout this component complains that child elements aren’t contiguous. Needless to say, the error message could be worded differently.

This code generates the error. Notice the comment to fix the error; move the layout tag to the first element after the declaration.

<?xml version="1.0" encoding="utf-8"?>
<s:Application name="Test" xmlns:s="library://ns.adobe.com/flex/spark">
<s:Group>
	<!-- border/background graphics -->
	<s:Rect width="100%" height="100%">
		<s:stroke>
			<s:SolidColorStroke color="0x000000" weight="2"/>
		</s:stroke>
	</s:Rect>
	
	<!-- move the layout information to the first declared elment
                 after <s:Group> to solve the error. -->
	<s:layout>
		<s:HorizontalLayout/>
	</s:layout>
	
	<!-- content of container -->
	<s:Label text="test" />
	<s:Graphic >
		<s:Ellipse width="12" height="12"
				   x="0" y="0">
			<s:fill>
				<s:SolidColor color="0xFF0000" />
			</s:fill>
		</s:Ellipse>
	</s:Graphic>
</s:Group>
</s:Application>

Looking at the documentation (see below) we see that ‘mxmlContent’ refers to the visible children of that mxml tag. The description clued me that the error message referred to layout and children of the container.

[Write Only] The visual content children for this Group. This method is used internally by Flex and is not intended for direct use by developers.
The content items should only be IVisualElement objects. An mxmlContent Array should not be shared between multiple Group containers because visual elements can only live in one container at a time.

If the content is an Array, do not modify the Array directly. Use the methods defined by the Group class instead.

Language Version:
3.0
Player Version:
Flash 10, AIR 1.5
Product Version:
Flex 4

Read Full Post »

3.5 to 4.0 Flex SDK upgrade

We have an application that requires upgrading from 3.5 to flex 4.0 SDK.

Needless to say our app did not compile on the first try with no code changes, that would be too easy. Digging around I found this great article on why the move to 4.0 enhances your ability to code following a MVC methodology:
Spark Styling.

This concept will hopefully allow us to change many of our sub-components into one .mxml file with different states. Looking forward to getting our code base converted.

UPDATED:

I wanted to put to words a general way for going about the upgrade process that I haven’t seen out on the web. Note I use ant tasks for building this flex project (contains java) but similar functions could be accomplished with IDE admin window.

1) Point ant to new Flex SDK 4.0, hold-breath and compile. Of course this doesn’t work, there were some api changes that required updating of the original code.

2) Try compile again. Now it’s complaining about fonts and halo themes and all other sorts of issues. Explicitly name the font renderer in the ant task, compile in compatibility mode 3.0, update namespaces and try again.

3) Now when I tried to add a new spark components it complains about being compiled in compatibility mode. Which makes it useless as a transition tool, and only served the purpose of convincing myself that I could in fact convert this application.

4) Luckily when I removed the compatibility option all the compiler complained about where attributes that had been abstracted to the spark skin layer. Since the whole point of converting was to take advantage of this new layout I just made note of all the attributes I removed and categorized them into skin classes I would need.

And now I am currently in the processes of learning skins … although my golf game is pretty rusty.

Read Full Post »

LDAP Authentication

Recently I needed a way to build authentication into a Flex application.

Instead of using a model baked into the Flex code that need recompilation, or other security concerns with flat files, I built a simple LDAP authentication java file, paired with a .properties file and is called via RemoteObject in flex.

I’ve posted code that uses simple authentication (no encryption over the wire) as this is an internal application, but it can be enhanced to do so.

The constructor builds an object that can connect to a LDAP source, all LDAP configuration is managed via the .properties file.

The heavy lifting is done by an authenticate method, returns a hash table (serialized as an object in flex). This allows me to see a) if they are authenticated, and b) what groups they are in using dot notation.

// Pseudocode -- remote object has a handler method, doesn't actually return an object (java code returns HashTable, serialized as object)
ldapAuthObj:Object = remoteObject.authenticate("username","password");

// now we can look at the object
if (ldapAuthObj.authenticated) { /** user was validated */ }
if (ldapAuthObj.GROUP_<name>) { /** user was in group */ }

java file

import javax.naming.*;
import javax.naming.directory.*;

import java.util.Hashtable;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Map;
import java.util.HashMap;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * This class to be used for authenticating against an LDAP server
*/
public class LDAPAuthenticationService {
	
	/** import these attributes from the config file */
	private static String INITIAL_CONTEXT_FACTORY;
	private static String PROVIDER_URL;
	private static String SECURITY_AUTHENTICATION;
	private static String BASE_DN;
	private static String LDAP_USERS_LOC;
	private static String LDAP_GROUPS_LOC;
	private static boolean LDAP_OVERRIDE;
	private static String LDAP_OVERRIDE_UNAME;
	private static String LDAP_OVERRIDE_PWORD;
	private static Map<String, String> groups;
	
	/** to allow connections from multiple methods */
	private DirContext ctx;
	
	/** track error messages */
	private String errorStack =""; // init to not null
	
	/** default constructor */
	public LDAPAuthenticationService() {
		// read in the properties file
		try {
			// go get the properties for this LDAP connection
			Properties configFile = new Properties();
			// load the file from the class path (moved there by ant task)
			configFile.load(this.getClass().getClassLoader().getResourceAsStream("/ldap.properties"));
			
			// send ouput to console
		//	enumerateContents(configFile.propertyNames());
			
			this.INITIAL_CONTEXT_FACTORY = configFile.getProperty("INITIAL_CONTEXT_FACTORY");
			this.PROVIDER_URL = configFile.getProperty("PROVIDER_URL");
			this.SECURITY_AUTHENTICATION = configFile.getProperty("SECURITY_AUTHENTICATION");
			this.BASE_DN = configFile.getProperty("BASE_DN");
			this.LDAP_USERS_LOC = configFile.getProperty("LDAP_USERS_LOC");
			this.LDAP_GROUPS_LOC = configFile.getProperty("LDAP_GROUPS_LOC");
			
			// get override info
			this.LDAP_OVERRIDE = Boolean.parseBoolean(configFile.getProperty("LDAP_OVERRIDE"));
			this.LDAP_OVERRIDE_UNAME = configFile.getProperty("LDAP_OVERRIDE_UNAME");
			this.LDAP_OVERRIDE_PWORD = configFile.getProperty("LDAP_OVERRIDE_PWORD");
			
			// init the array list
			groups = new HashMap<String, String>();
			// load the groups into a String array
			for (Enumeration e = configFile.propertyNames() ; e.hasMoreElements() ;) {
				String key = e.nextElement().toString();
				if (key.indexOf("GROUP_") == 0) { // ie key in key=value pair matches "GROUP_"
					// append the group name to the array list for checking later
					groups.put(key,configFile.getProperty(key));
				}
			}
			
		}
		catch (FileNotFoundException e) {
		//	e.printStackTrace();
			System.err.println("FileNotFoundException: "+e.getMessage());
			errorStack+=e.getMessage()+"\n";
		}
		catch (IOException e) {
			// TODO set defaults, or just give up?
		//	e.printStackTrace();
			System.err.println("IOException: "+e.getMessage());
			errorStack+=e.getMessage()+"\n";
		}
		
	}
	
	/**
	 * This method will test if a user has access to the LDAP, if so
	 * it will then check the list of groups and check for is access
	 * 
	 * @param String username as named via a uid in the LDAP
	 * @param String password clear text in LDAP
	 * @return Hashtable authenticate object
	*/
	public Hashtable authenticate (String username, String password) {
		
		Hashtable<String,Boolean> authHT = new Hashtable<String,Boolean>();
		
		// assume they will not pass the test
		boolean authenticated = false;
		
		// first check to see if we even need to hit LDAP (not overridden)
		if (this.LDAP_OVERRIDE) {
			System.out.println("Override Authentication");
			// just check against stored username/password, put in all groups
			if (username.equals(this.LDAP_OVERRIDE_UNAME) && password.equals(this.LDAP_OVERRIDE_PWORD)) {
				authenticated = true;
				// just add then to each group
				for(String key : groups.keySet()) {
					// push the name of the group and access to it boolean
					authHT.put(key,true); // method throws NamingException
				}
			}
			
		}
		else { // authenticate agianst LDAP
			System.err.println("LDAP Authentication: " + username);
			// build a hash table to pass as a bindable event
			// Set up environment for creating initial context
			Hashtable<String,String> env = new Hashtable<String,String>(); 
			
			env.put(Context.INITIAL_CONTEXT_FACTORY,this.INITIAL_CONTEXT_FACTORY);
			env.put(Context.PROVIDER_URL, this.PROVIDER_URL);
		
			env.put(Context.SECURITY_AUTHENTICATION, this.SECURITY_AUTHENTICATION);
			// we take the uid to authenticate, pair it with the username, and append the base location
			env.put(Context.SECURITY_PRINCIPAL, "uid="+username+","+this.LDAP_USERS_LOC+this.BASE_DN);
			env.put(Context.SECURITY_CREDENTIALS, password);
			
			// send ouput to console
		//	enumerateContents(env.elements());
			
			try {
				// first we want to connect to the LDAP Server and create initial context
				// making sure the user name and password are valid
			    ctx = new InitialDirContext(env); // Throws AuthenticationException if not valid username/password
				// WE NEVER GO PAST HERE IF AuthenticationException THROWN
				System.err.println("made init connect");
				// made it past the
				authenticated = true;
				
				// now that we have verified they are a valid user, lets see what type of access they have
				// groups are specified in the config file as "GROUP_<name>" key=value pairs where value is the LDAP group name
				// and key is what we are looking for in the scheduling app
			    for(String key : groups.keySet()) {
					// push the name of the group and access to it boolean
					authHT.put(key,new Boolean(userInGroup(username,groups.get(key)))); // method throws NamingException
				}

			    // Close the context when we're done
			    ctx.close();
			}
			catch (AuthenticationException e) {
				// binding to LDAP server with provided username/password failed
				// e.printStackTrace();
				System.err.println("AuthenticationException: "+e.getMessage()); // outputs -> [LDAP: error code 49 - Invalid Credentials]
				errorStack+=e.getMessage()+"\n";
			} 
			catch (NamingException e) {
				// catches invalid DN. Should not be thrown unless changes made to DN
				// Could also fail from the context of the called method userInGroup
				System.err.println("NamingException: "+e.getMessage());
				//e.printStackTrace();
				errorStack+=e.getMessage()+"\n";
			}
		}
		
		// push whether or not it was authenticated
		authHT.put("authenticated",new Boolean(authenticated));
		
		/** spill contents to catalina.out file
		enumerateContents(authHT.keys());
		enumerateContents(authHT.elements());*/
		
		return(authHT);
	}
	
	/** return any failure codes. Since we only return boolean from
	 * authenticate method. Good idea to have way to see error
	 */
	public String getAuthenticateError () {
		System.err.println(errorStack); // send to catalina.out log file
		return errorStack;
	}
	
	/**
	 * after a user has successfully logged in we want to build
	 * an access object for use in the scheduling system
	 *
	 * @param String username
	 * @param String group a group name to check for username in (via memberUid string)
	 * @return boolean yes or no in the group
	 * @throws NamingException when the search fails by DN this will be thrown
	 */
	private boolean userInGroup (String username, String group) throws NamingException {
		// assume they are not
		boolean inGroup = false;
		
		// Specify the attributes to match
		Attributes matchAttrs = new BasicAttributes(true); // ignore attribute name case
		// set the common name for the group
		matchAttrs.put(new BasicAttribute("cn",group)); // named group for access rights

		// Search for objects that have those matching attributes in the specified group location
		NamingEnumeration answer = ctx.search(this.LDAP_GROUPS_LOC+this.BASE_DN, matchAttrs);
		
		// search for that user id in the member list
		while (answer.hasMore()) {
		    SearchResult sr = (SearchResult)answer.next();
		    if ((sr.getAttributes().get("memberuid").toString()).indexOf(username) >= 0) {
				// this user is in the specified group
				inGroup = true;
			}
		}
		System.err.println(username + " in " + group + ": "+new Boolean(inGroup).toString());
		return inGroup;
	}
	
	/** useful for diagnostic infomration, spit out a set of elements
	  * say in a hashtable or properties file
	  */
	private void enumerateContents(Enumeration e) {
		while (e.hasMoreElements()) {
			System.err.println(e.nextElement());
		}
	}
}

.properties file

# This is a config file for specifing some generic information
# about the LDAP location to avoid recompiling the source code

# most of these key=value pairs use a Context.<name> as a key

INITIAL_CONTEXT_FACTORY=com.sun.jndi.ldap.LdapCtxFactory

# space separated values for more than one ldap server
PROVIDER_URL=<url>

# supports none, simple, strong
SECURITY_AUTHENTICATION=simple

# start a name with the base DN (where all LDAP objs are)
BASE_DN=<dn>

# Name other Locations
# TRAILING COMMA REQUIRED. so LDAP_USERS_LOC + BASE_DN = fully qualified name
# allows for ease of sub directory traversing if needed in the future

# from BASE_DN where are all users stored
LDAP_USERS_LOC=<users>,
# from BASE_DN where are all the groups
LDAP_GROUPS_LOC=<groups>,

### In case of LDAP communication failure (or manual override, say for local development)
LDAP_OVERRIDE=false
LDAP_OVERRIDE_UNAME=dev
LDAP_OVERRIDE_PWORD=user

### IMPORTANT: All group names need to start with "GROUP_" that is how the 
### java class knows what groups to authenticate for. IE it goes though all
### the file's attributes, looks for GROUP_ the value is the group name in LDAP

### Left side (key) is name in Flex app to verify against
### Right side (value) is group name in LDAP server
### So java uses the "GROUP_" key/values in a HashMap and verifies the user 
### is in the LDAP group (value) and assigns a boolean to the key

# groups
GROUP_A=<ldap group>
GROUP_B=<ldap group>
GROUP_C=<ldap group>

Read Full Post »

%d bloggers like this: