SharePoint Recruiters Shouldn’t Suck At Their Jobs

Generally that is what I am discovering is the case, however terse that may appear. I normally don’t peruse recruiter sites, except I got this outlandishly awesome job offer housed in one. Before I knew it, I was searching for absolute randomness on the site for fun. As I was browsing for SharePoint jobs, I established what looked to be the average SharePoint job ad, and I chuckled a bit.

I will in fact copy and paste it directly from the offered advertisement text:

SharePoint Programmer / Developer / Architect: Must have at least 7 years practical work experience in C# with building solutions on top of SharePoint. 7 years Java experience to support conversions of J2EE to .NET solutions. To support this role, 5 years of Oracle and Delphi experience preferred. 5 years experience required with MFC/ATL and Crystal Reports.

Must have 5 years of deep architectural experience with building large SharePoint farms, and administrating them in the same capacity including deployed custom development. Several years architecting and developing against ISA and MIIS preferred and encouraged, however not required.

Interestingly enough, on recruiter sites you are able to spot precisely when the job was posted. The antiquated date appended to this made sense to me since this particular position has been open for over half a year, borderline 8 months. And I will wager that no sensible applicant has dared to apply to this gobbledygook.

Why?

Because not only does the ad undoubtedly convey that the posting recruiter did inadequate technical research because of the erroneousness of technical dating (which offends job seekers btw), but they must be looking for the fabled nine armed, four brained mythical programmer that they prophecies foretell of, because for all intents and purposes those requirements are outrageous.

First, let’s pick apart the ad.

7 years of C# development experience, while being feasible, is marvelously atypical since the establishment of the C# language in common commercial markets is roughly in late 2000. So, this organization is most likely looking for a C# beta tester for when Anders Hejlsberg was still calling it code name Cool. Needless to say, this would be exceptional and uncommon.

7 years of .NET development on top of SharePoint I can see being a qualification, if STS (2001) development actually used .NET. Then it would be cool. But it didn’t. So the basic arithmetic of the recruiter sucks. Even then, you would be subject to the same time considerations as described above, making it not necessarily common.

5 years experience with MFC/ATL? Huh? I mean, I did MFC / ATL programming for a long time (it paid for college), and I have to admit I really don’t use that skill set outside of the far reaching development concepts very often. It just isn’t obligatory for ordinary SharePoint business development (note the use of the word ordinary there).

I actually can understand the Crystal Reports one. I guess. I mean I don’t use it, but it seems like in some circles it could be interpreted as a pragmatic requirement. I guess.

The conversions requirement made me sort of laugh, since that is a pretty specific skill set to want out of a developer. Because of this, as well as the larger noted SharePoint development experience, I think the organization might be more successful with breaking this into two separate jobs. To me, it’s kind of like walking into a barbershop, getting a haircut, and then asking why the barber didn’t go wash your car and walk the dog. Lazy bastard. I think coupling a C# developer that has rudimentary Java skills with a Java developer with basic C# skills would produce higher quality code conversion, most likely in a more reasonable time frame since those two programming schools tend to stay siloed in their language of choice. Even though in my experience code conversions just end up being a software rewrite anyways.

When the recruiter starts to talk about wanting the SharePoint developer, architect, and administration full package, I start to get a little frustrated. While there are certainly people that can wear all these hats, very well too, generally they become too busy divvying themselves up between tasks spawned from owning those assorted roles habitually those tasks never near completion. Only little increments are accomplished, never leading to closing. People often times like to specialize in a certain aspect additionally, and pride themselves in knowing all the ins and outs of that niche. I like doing architecture, but I love doing development. Furthermore, I simply loathe administration. It doesn’t mean I can’t do it; I just don’t enjoy it as a job role. It doesn’t blow my hair back.

The ISA and MIIS requirements are asinine if it is anything but a proof of concept or a small deployment. This should be principally noted with the MIIS requirement; the recruiter should be looking for someone that continually focuses on identity management systems when standing that up. Knowing those concepts to the metal vastly increases chance of project success. Like I said in a previous point, you don’t ask your barber to cut your grass. Unless your barber is just genuinely charitable and bored.

Consequently what happens when you are the recruiter that placed this ad out into the wild? You do a dance of joy because low and behold you found a hidden genius that satisfies hopeless requirements (not just meaning that they are hard, they really are impossible)! Yeah! Diamond in the rough! You rule! And he is willing to do it for 40k a year!

But what does your organization end up with?

The quintessential, yesss-sssir guy, whom only has the qualification of being a professional impostor and habitual liar. Fundamentally you have someone with an archetypal psychological complex tailored around satisfying people even though that satisfaction is bogus. Meaning, they are full of shit. I am not saying that to satisfy all the requirements is impossible (assuming that technology timeframes were right). It’s not, however it is decidedly unlikely. More than likely, they might *know* these buzzwords, read about it a bit, but don’t have deep experience. I have minimal Delphi experience, and very, very brief Oracle experience. Although I have used these, I don’t list them as skill sets of mine. I am not proficient enough. If they were mentioned at a job interview as required, I would throw sand in the interviewer’s eyes and flee on foot.

So I propose the following to SharePoint recruiters…

Firstly, be sensible with your project needs. I know it would be nice to have a methamphetamine IV feed developer that consistently practiced other technology facets locked in the closet that only required fish heads for substance doing your project work. I wouldn’t mind one of those myself. But it isn’t rational. When defining roles for a SharePoint implementation during the project planning process, it is healthier to have more jobs divvied up based on specialization rather than constrained numbers trying to overexert themselves out of their specialty. The former has proven, at least in my personal experience, while taking a large toll on budget tends to lead to enhanced quality of deliverables and probability of aggregate project success.

Secondly, tailor their SharePoint job ads to be more specific. Don’t be so vague. You clearly have a project, with a defined project scope (hopefully), project WBS’s (even though you are mocking people), and other project documentation. I don’t understand why this isn’t kept in mind when you are making your staffing decisions. I appreciate keeping it ambiguous because who doesn’t like the brain-surgeon-that-worked-charities-while-saving-puppies-from-burning-houses-but-now-does-awesome-SharePoint-development-guy, but your advertisement requirements are not practical. One of the most important portions of project planning is to define the roles filled by project team members and the responsibility of each role. I really doubt that there is just one role defined as everything besides the project manager.

Lastly, for Christ’s sake, research the technology that you are advertising for. You don’t have to know it very well, you just have to know it enough to *find* someone that knows it a whole lot more than you do. If I wanted to find a good dairy farmer to get some milk, I don’t have to know how to milk a cow or manage a farm, I just have to know the basics of the milk I want. The same goes when you are building technology requirements. Kinda.

Share

ADAM (Active Directory In Application Mode) Custom Role Provider

Introduction to the Standard ADAM Role Provider
ADAM (Active Directory on Application Mode) has become increasingly common for companies that want to implement a featherweight directory protocol to use within their SharePoint environment. With ADAM, setting up role based security principles is common because it is an extendable architecture that provides the benefits of Active Directory without the management and implementation overhead, and also allows the concept of binding roles to operations which is a very helpful function since beyond acting as a role and user data store, the ADAM instance can become an engine by which to run an mixed, integrated SharePoint and miscellaneous application environment. The AzMan role provider limits the users that you can integrate with the LDAP pluggable provider to those that resolve to a tangible windows identity (it must be a domain account), which of course is a problem for people that wish to use ADAM only users. With increased support for ADAM in SharePoint 2007, the requirement from customers has become even more prevalent and for some, the lack of role support has become a cause for concern.

The Need for a Role Provider
In other articles in the site (notably here) there have been detailed explanations that go into using the AvtiveDirectoryMembershipProvider in your SharePoint environment and how to do so, and this provider is indeed incredibly central when implementing ADAM within your SharePoint environment for user authorization. However, it is necessary to implement a custom provider that will support the same role type functionality that is available to other providers to those same ADAM users, allowing one to bypass the limitation of only supporting assigning roles to actual domain accounts.

The provider that comes with the default instance of ADAM is AuthorizationStoreRoleProvider, which couples with the functionality that is provided by the Authorization Manager. The actual communication happens with a primary interop (microsoft.interop.security.azroles.dll), which is a COM object (discussed shortly) imported from the type library AZROLESLib. The membership functions of ADAM with the latest releases of ADAM is supported, and storing principles without resolving to Windows identities works fine.
Role Provider Functionality
There are various layers of supported functionality that exist in the default role provider that are extended in the custom provider that is available here. Notably, AddUsersToRoles, CreateRole, DeleteRole, FindUsersInRole, GetAllRoles, GetRolesForUser, GetUsersInRole, IsUserInRole, RemoveUsersFromRoles, RoleExists, and other lower level functions like getting application names. Most of the methods that are in the ADAM role provider should resemble other providers that must be built in order to support database transactions of other origins, whether it is Oracle, flat-text, DB2, or Informix, the ADAM role provider will have several characteristics that are the same.

There are some important best concepts that must be considered with the ADAM role provider. The first is to take the concept of application name into consideration, in this case it is done by referencing the ADAM application which will in turn provide the methods that are needed in order to talk back forth between the role provider and the ADAM backend datastore, this is important in order to allow segregation of data points provided by the application name so that multiple vectors can exist. Because multiple applications can exist that are used by a provider, an attribute can be determinably expressed for provider functionality. If in the configuration files an application name is not specified, then the default application name as determined by the .Net framework. This can be extended further when assimilating the concept of ADAM application scopes which allow you to drilldown even further.
Since scopes are a lot less common than scopes, lets see a brief overview on getting an application name.


The first thing that we have to do is initialize a new AuthorizationStore object by calling a new AzAuthorizationStoreClass(). Following, we have to get the values that we require out of the configuration sections (the connection strings that are specified), and then call OpernApplication() out of microsoft.interop.security.azroles.dll and return the application name.

Firstly, there is the concept that the provider will have to inherit out of the RoleProvider base class, which derives itself out of the System.Web.Security namespace. Unlike other concepts introduced on the site, such as using Façade patterns (factory patterns that develop a generic interface for providing a certain set of functionality, see here) for transactional provider interaction, the ADAM role provider is slightly more specific, i.e. as opposed to database factory patterns that are abstract the methods introduced are in fact concrete by nature.

Role providers are quite easy to write and to implement, because they are typically just sealed or abstract classes, and the parameters that are massaged are very simplistic, they are role names, and the users that are associated with those role names. There are a couple methods that should always be implemented, and others that although may be considered optionally, are nonetheless very important.


As an example, lets examine the most obvious method, creating roles. This is done by using the CreateRole methods, which will simply create a new role in the backend datastore. There are of course some conditions that must be checked before connecting to the datastore that ADAM provides before you actually create the new role before executing committing actions. The first is to examine illegal character conditions, and the second is that the role name doesn’t firstly exist. Checking whether the name already exists can be achieved by simply iterating through the available roles and then determining whether the roleNames that are being returned match those that are passed in as a parameter:


The second condition to check is whether there are illegal characters that are in the role name. This is done by doing an if statement and using an IndexOf in order to tell whether the passed in character string is indeed in the role name. The only one that is technically illegal with a role provider is actually a comma, however if you have standards that you must implement in order to conform to a role name requirement, you can use the same type of technique.

After the conditions are met, it is time that you can actually add the role that you want.
The methods that are needed as you can see are pretty simple and easy to implement, although this article won’t detail them all, there are some central concepts that should be covered.

Configuration Elements
As with the other providers that are implemented for your SharePoint environment, one of the most important changes that you will make is the configuration elements for the web application. This is accomplished in two different places, the connection strings elements so that the LDAP connection can be made and the role manager section which will specify the provider type, reference to the connection strings settings, and the application name since it will help define scopes for your business applications.
The first of these is to setup the configuration string which will define the connections that are made to the ADAM data store.

[xml]
< connectionStrings >
connectionString=
LDAP://ADAM.sharepointsecurity.com/OU=Example,DC=sharepointsecurity,DC=com/ >

[/xml]

The second this is to enable the Role Manager, set the default provider to the ADAM role provider, and then set the application name in the configuration elements. The type name is the custom role provider name, in this case ARB.ADAM.RoleProvider.

[xml]
< roleManager enabled=true defaultProvider=directoryProvider >
< providers >
< clear / >
< add name=ADAMRoleProvider type=ARB.ADAM.RoleProvider connectionStringName= ADAMStoreConnection applicationName=SharePoint/ >

[/xml]

Share

Adding Users To SharePoint With A Custom Membership Provider

Arguably the most significant function that you will execute against your membership provider data store is creating your SharePoint users that will be authenticated against your custom membership database. In this section, I will detail how the Universal Membership Provider will add users and in the next article we can see important security centric user methods, notably encrypting passwords and password answers, and how to use the machine key or the .NET cryptographic hash algorithm to achieve this task.

Creating New Users in the Membership Provider Data Store
The foremost task in creating a SharePoint user is setting up the class that is going to perform the function with the appropriate parameters. The parameters we are adding should be pretty self evident, we are passing the:

  • username string
  • password string
  • email string
  • passwordQuestion string
  • passwordAnswer string
  • isApprovied boolean value
  • userId object
  • status out of the MembershipCreateStatus enumeration

[csharp]

public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object userId, out MembershipCreateStatus status)

[/csharp]

There are two important portions to note for this particular class. The first is that we are overriding it because we are modifying the implementation of an inherited method, in this case MembershipUser, derived out of the System.Web.Security namespace, which allows us to update various pieces of information in the user data store.

Following, we are are going to set up the connection to the database by using DBconnection which will embody this connection:

[csharp]
DbConnection universalDBconnection = null;

[/csharp]

Next, we must construct the statement out of an interface provided by the System.Data namespace that will be executed when connected to the custom membership data source. We can use an interface because we are allowing classes to implement the methods defined in IDbCommand, in essence, we will create an instance of a class that will inherit from IDbCommand:

[csharp]

IDbCommand universalDBcommand = null;

[/csharp]

Then we have to determine if the user was created successfully. This is accomplished by using the MembershipCreateStatus enumeration which will parse out the result out of our CreateUser operation:
MembershipCreateStatus sharepointUserStatus = MembershipCreateStatus.Success;
Lastly, we are going to then expose the SharePoint user that we are going to create in the membership data store:

[csharp]
MembershipUser sharepointUser = null;

[/csharp]

Now, when creating the user, there are a a small number of things that we have to validate, and whether these conditions conform to what we require. Establishing an “if” loop to iterate through various problems prior to executing the creation of the user so we can throw any pre-commitment exceptions if they should occur during the processing. What we are going to check for is:

  • if the password question is valid
  • if the password answer is valid
  • if the username is valid
  • if the password is valid

This is done by using the MembershipCreateStatus enumeration (talked about in the above section) and ProviderException out of the System.Configuration.Provider namespace to throw intuitive messages if a problem does occur :

[csharp]
if (this.RequiresQuestionAndAnswer && string.IsNullOrEmpty(passwordQuestion))
{
sharepointUserStatus = MembershipCreateStatus.InvalidQuestion;
throw new ProviderException(“The question entered is not valid”);
}
if (this.RequiresQuestionAndAnswer && string.IsNullOrEmpty(passwordAnswer))
{
sharepointUserStatus = MembershipCreateStatus.InvalidAnswer;
throw new ProviderException(“The answer entered is not valid.”);
}
if (string.IsNullOrEmpty(username))
{
sharepointUserStatus = MembershipCreateStatus.InvalidUserName;
throw new ProviderException(“The user name is not valid.”);
}
if (!this.IsStrongPassword(password, out userStorage))
{
sharepointUserStatus = MembershipCreateStatus.InvalidPassword;
throw new ProviderException(userStorage);
}

[/csharp]

Now we are almost ready to create the user. We have checked their password, it met our conditions, and all their other information seems to fit in with enterprise standards. The next part is actually performing hashing on the password and passwordanswer. This is the subject for the next article, however at this point we will call the hashing function by:

[csharp]
password = this.hashPassword(password);
passwordAnswer = this.hashPassword(passwordAnswer);

[/csharp]

Since this calls a separate portion, it is discussed more exhaustively in a subsequent section. For the time being it is safe to just be aware that exists.

Now we are ready to start talking to the database. We are going to start by opening up a channel to the custom database to start making the relevant changes that we need while maintaining a proper locking behavior for the connection mechanism (using IsolationLevel). Firstly, we are going to simply going to create a new instance of the connection to the MOSS / WSS v3 membership data store and make the relevant connection string settings.

[csharp]

universalDBconnection = this.provider.CreateConnection();
universalDBconnection.ConnectionString = this.connectionString;

[/csharp]

Then we are going to open the MOSS / WSS v3 membership data store by using those same connection string settings by calling Open() on the connection. Following, we are going to call the base class for the transaction and start the database connection by using shared locks to avoid dirty reads (this could however result in phantom data, but is a proper isolation level for this implementation purpose).
[csharp]

ouniversalDBconnection.Open();
DbTransaction sharepointMembershipTransaction = universalDBconnection.BeginTransaction(IsolationLevel.ReadCommitted);

[/csharp]
Now we are at a pivotal portion of the user creation process since we are going to be writing to the database. Before we massage the data in, we are going to have to build some compensation in for a userID since it is the unique identifier for all user records.

[csharp]

if ((userId != null) && !(userId is Guid))
{
throw new ArgumentException(“userId”);
}
if (userId == null)
{
userId = Guid.NewGuid();
}

[/csharp]

If a new GUID is created successfully, then we can call the CreateUser object and observe the success of our initial transaction (to check whether is was successful or not).

[csharp]

this.usersProvider.CreateUser(oracleDBconnection, false, username, userId, true, out status);
if (status != MembershipCreateStatus.Success)
{
sharepointUserStatus = status;
return sharepointUser;

[/csharp]

Now, we are going to add the relevant information about the user, such as their:

  • UserID
  • Password
  • Email
  • Passwordquestion
  • Passwordanswer
  • isApprovedState
  • isLockedOutState
  • CreationDate
  • LastPasswordChangeDate
  • LastLockOutDate
  • Email
  • Passwordquestion
  • Passwordanswer
  • isApprovedState
  • isLockedOutState
  • CreationDate
  • LastPasswordChangeDate
  • LastLockOutDate

Most of this should look very familiar, is is resembling the sqlmembershipprovider database setup by the SQL application server wizard when registering the initial database creation, see the table here.
This is where things will begin to get very database specific. We are going to have our first instance of using whatever database query language that you are using for your custom provider in this section. Looking at the code, it is pretty simple.

[csharp]

universalDBcommand = this.CreateCommand(“INSERT INTO aspnet_membership (email, passwordquestion, passwordanswer, isapproved, islockedout, creationdate, lastpasswordchangeddate, lastlockoutdate, userid, password) VALUES ()”, universalDBconnection);

[/csharp]

In this example, I am using what could either by Oracle or PostgreSQL, however this should translate into nearly every other syntax. We are calling CreateCommand to create a command object associated with the universalDBconnection, the last parameter between the (). The VALUES section is left intentionally blank, since this will often vary between systems.

Following, we are going to Get the IDataParameter collection and use add to add items to the IList. These items are all the relevant fields to a user as defined in the INSERT statement from above.

[csharp]
universalDBcommand.Parameters.Add(this.CreateParameter(“email”, DbType.String, 0xff, email));
universalDBcommand.Parameters.Add(this.CreateParameter(“passwordquestion”, DbType.String, 0xff, passwordQuestion));
universalDBcommand.Parameters.Add(this.CreateParameter(“passwordanswer”, DbType.String, 0xff, passwordAnswer));
universalDBcommand.Parameters.Add(this.CreateParameter(“isapproved”, DbType.Boolean, isApproved));
universalDBcommand.Parameters.Add(this.CreateParameter(“islockedout”, DbType.Boolean, false));
universalDBcommand.Parameters.Add(this.CreateParameter(“creationdate”, DbType.DateTime, currentTime)); universalDBcommand.Parameters.Add(this.CreateParameter(“lastpasswordchangeddate”, DbType.DateTime, currentTime));
universalDBcommand.Parameters.Add(this.CreateParameter(“lastlockoutdate”, DbType.DateTime, currentTime));
universalDBcommand.Parameters.Add(this.CreateParameter(“userid”, DbType.Guid, 0xff, userId));
universalDBcommand.Parameters.Add(this.CreateParameter(“password”, DbType.String, 0xff, password));

[/csharp]

The only part of this that may appear confusing is the 0xff. This is simply defining 255 characters, which should be plenty of space.

Finally, we are going to call ExecuteNonQuery() in order to execute all of statements against the connection object, and cause an affect on the relevant rows. Once the ExecuteNonQuery() has completed, we can create a New MembershipUser object by:

[csharp]
sharepointUser = new MembershipUser(this.Name, username, userId, email, passwordQuestion, null, isApproved, false, currentTime, currentTime, currentTime, currentTime, currentTime);
sharepointMembershipTransaction.Commit();

[/csharp]

and lastly by committing the database transaction by calling Commit().

We do also have to compensate for exceptions as the occur, which can be done by simply doing a standard exception block, and then using Rollback() to push the transaction back to its pending state.

[csharp]

catch (Exception userTransactionException)
{
sharepointMembershipTransaction.Rollback();
throw userTransactionException;
}

[/csharp]

In the next exception handler, we have to determine whether the provider returned an error that is not described by other MembershipCreateStatus enumeration values:

[csharp]
catch (ProviderException createUserStatusNotDescribedException)
{
if (this.WriteExceptionsToEventLog)
{
this.WriteToEventLog(createUserStatusNotDescribedException, “CreateSharePointUser”);
}
if (sharepointUserStatus == MembershipCreateStatus.Success)
{
sharepointUserStatus = MembershipCreateStatus.ProviderError;
}
throw createUserStatusNotDescribedException;
}

[/csharp]
And lastly, our final exception during the Create User process, we have to handled internal errors that may occur within the provider that may not map to any pre-existing exception handling routine by calling ProviderException:

[csharp]
catch (Exception generalProviderExceptionError)
{
if (this.WriteExceptionsToEventLog)
{
this.WriteToEventLog(generalProviderExceptionError, “CreateSharePointUser”);
}
throw new ProviderException(this.exceptionMessage, generalProviderExceptionError);
}

[/csharp]
The last step is to not leave the database connection open since we are through using it:

[csharp]
finally
{
if (universalDBconnection != null)
{
universalDBconnection.Close();
}
}

[/csharp]

Share