Developer's Guide

Configuration Settings

Developer's Guide : Configuration : Configuration Settings Overview

Overview

Configuration settings are very important for integrating with MemberSuite’s API. They allow you to test for things that are part of an association’s particular configuration.

As a developer, you’ll want to avoid hard coding items in your software that could change. Additionally, you might want to reference configured items in the association’s system. This presents a couple of unique challenges.

  1. The first challenge is configuration. If you store your configuration in a local file, the only way you can change it is to open the file directly on your server. This is obviously not ideal.
  2. The second challenge is if your code relies on a custom field being present to add a value into that field and someone deletes that field, your code would break immediately.

Using configuration settings solves both of these problems.

Link configuration setting to field or object

A configuration setting can be a simple name/value pairing or it can be linked to a configuration object in MemberSuite.

If you link a configuration setting to a field or other object in MemberSuite, the setting cannot be deleted. This means you can be sure that the dependencies of your application will remain intact, impervious to accidental deletion by an unsuspecting client.

Another advantage to using a configuration setting is that you can create abstracts for parts of your code.

Example

You have code in the portal that checks to see if a member is associated with a particular membership type, such as Alumni. The code looks like this:

if ( member["Type"] == "594811f7-0006-4feb-b7c1-ae841b42a9c2")

{ Response.Write("Your membership is invalid.");

This is brittle code because if the code is changed it can pose two problems:

  1. The code will break if the membership type (that corresponds to the ID) is deleted from the configuration. However, using a configuration setting would keep people from deleting a configuration relied on by external code.
  2. If you make a sandbox of the association and you want to test new website code in the sandbox, the Membership Type ID in the code belongs in the original association, not in the sandbox. You would have to change it to the sandbox ID, test it, and change it back when you go live.

Instead, you could create a configuration setting named CFG_MEM_ALUMNI, and write the following code:

var alumniTypeID = proxy.GetConfigurationSetting( "YourDomain","CFG_MEM_ALUMNI").ResultValue;

if ( member["Type"] == alumniTypeID) { Response.Write("Your membership is invalid");

Using this code means that:

  1. MemberSuite automatically prevents someone from deleting the code. If someone tries to delete the code, a message appears letting him or her know that some part of the MemberSuite code relies on the setting and it cannot be deleted.
  2. When you make a sandbox for an association, MemberSuite also moves the configuration settings for the association to the sandbox. Your code will point to the correct ID, no matter what environment the application is in—sandbox or production. This is not possible with a hard-coded ID.

API

Developer's Guide : API : API Overview

Overview

The MemberSuite System was designed with a Service Oriented Architecture. Simply speaking, this means that while object-oriented principles dominate the internals of the system, all significant functionality provided by the System is exposed via a series of endpoints, or services. The services comprise our Application Programming Interface, or API, and allow external applications access to System functionality. The baseline MemberSuite user interface and surrounding services makes use of exactly these services to do their work. The result of this architectural design is a clean separation of concerns between the User Interface, running agents and jobs, and the core of MemberSuite itself. The other benefit is the ability of external developers to extend, integrate with, and leverage the MemberSuite platform for their customers.

Developer's Guide : API : Configuration Settings

As a developer, you’ll want to avoid hard coding items in your software that could change. Additionally, you’ll want to reference items in the association’s system that are configured. This presents a couple of unique challenges.

The first is configuration; if you store your configuration in a local file, the only want it can be changed would be to access this file directly on your server. This is obviously not ideal.

The second is worse; let’s say your code relies on a custom field being present to add a value into that field. What happens if someone delete’s that field? Your code would break immediately.

To solve both of these problems, we have the concept of Configuration Settings. A Configuration Setting can be a simple name/value setting, or it can be linked to a configuration object in MemberSuite. If you create a linked configuration setting – to, say, a custom field – no one will be able to delete the object out of the system. This means you can be sure that the dependencies of your application will remain intact, impervious to accidental deletion by an unsuspecting client.

We strongly suggest that you use Configuration settings. The other huge advantage is that you can abstract away certain parts of your code. Say you have code that checks to see if a member is of a particular membership type, say Alumni. How do you do that? Let’s say you write code like this:

<code><span class="hljs-keyword">if</span> ( member[<span class="hljs-string">"Type"</span>] == <span class="hljs-string">"594811f7-0006-4feb-b7c1-ae841b42a9c2"</span>) 
{
</code> <code>Response.Write(<span class="hljs-string">"Your membership is invalid"</span>);
}</code>

OK. But this is brittle; what happens if the ID changes? If you have to point your code at another association? Instead of doing this, you could create a configuration setting named CFG_MEM_ALUMNI, and do this:

var alumniTypeID = proxy.GetConfigurationSetting("YourNamespace","CFG_MEM_ALUMNI").ResultValue;

if ( member["Type"] == alumniTypeID) { Response.Write("Your membership is invalid"); }

Developer's Guide : API : How To: Create Configuration Settings

To Create Configuration Settings:

  1. Go to Setup
  2. Scroll down to Advanced Settings
  3. Go to Configuration Settings

Developer's Guide : API : Search in MemberSuite

Overview

The most common thing you will do in MemberSuite is to pull data out of the system. The mechanism for querying data in MemberSuite is called Search. There are two primary methods for search:

  • MemberSuite Query Language (MSQL) – this is a SQL-like language that allows you to select, insert, and update records
  • Search object – this is a object that represents a search, including search criteria, output columns, and sorting

MemberSuite provides an easy service for converting a MSQL string to a Search object, because internally, MemberSuite uses Search objects for everything.

MemberSuite Query Language (MSQL)

The MemberSuite Query Language is a SQL-like language that let's you SELECT/INSERT/UPDATE records. The basic syntax:

<code><span class="hljs-keyword">SELECT</span> <<span class="hljs-keyword">columns</span>> <span class="hljs-keyword">from</span> <searchType> <span class="hljs-keyword">where</span> <criteria>
<span class="hljs-keyword">UPDATE</span> <record> <span class="hljs-keyword">SET</span> <fieldsToSet> <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">ID</span>=<<span class="hljs-keyword">id</span>> 
<span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> <record> (<<span class="hljs-keyword">columns</span>>) <span class="hljs-keyword">VALUES</span> (<valuesToInsert>)
</code>

You can execute MSQL via the ExecuteMSQL method in the API. Some examples of MSQL queries:

<code><span class="hljs-keyword">Select</span> FirstName, LastName <span class="hljs-keyword">from</span> Individual <span class="hljs-keyword">where</span> Email <span class="hljs-keyword">like</span> <span class="hljs-string">'%gmail%'</span> <span class="hljs-keyword">or</span> (Age><span class="hljs-number">15</span> <span class="hljs-keyword">and</span> Gender_c=<span class="hljs-string">'Male'</span>) <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> CreatedDate
<span class="hljs-keyword">Insert</span> <span class="hljs-keyword">into</span> Individual (FirstName, LastName) <span class="hljs-keyword">values</span> (<span class="hljs-string">'Andrew'</span>, <span class="hljs-string">'Ryan'</span>)
<span class="hljs-keyword">Update</span> Individual <span class="hljs-keyword">set</span> LastName=<span class="hljs-string">'Ryan'</span> <span class="hljs-keyword">where</span> <span class="hljs-keyword">ID</span>=<span class="hljs-string">' 594811f7-0006-4feb-b7c1-ae841b42a9c2'</span>
</code>

In order to know what to query, and what fields are available, check the Object and Search reference at the end of this manual.

The Search Object

Search Object Class Diagram

Usually, you will find it easier to use MSQL for querying data. It's simpler and easier to use. However, there may be times when want to construct your own search object, particularly if you're designing a search programmatically.

To the right is the class diagram for the search object.

At the base of a search object is a SearchOperation, which is a single operation/criterion from a search. A SearchOperationGroup is child of SearchOperation, as it is a special type of criterion that has child criteria. A Search is the principle object, which contains:

  • Type – the search type. This is usually the object type, like Individual or Organization
  • Context – if the search has a context (for instance, searching for event registrations for an event)
  • Criteria – a collection of SearchOperations that represent the search criteria
  • Output Columns – a collection of columns that represent the output of the search
  • Sort Columns – a collection of columns that represent the sort order

Because Criteria can contain SearchOperationGroups, you have the ability to design parenthetical queries. Consider the following MSMQL statement:

<code><span class="hljs-keyword">Select</span> FirstName, LastName <span class="hljs-keyword">from</span> Individual <span class="hljs-keyword">where</span> Email 
<span class="hljs-keyword">like</span> <span class="hljs-string">'%gmail%'</span> <span class="hljs-keyword">or</span> (Age>15 and Gender_c='Male') order by CreatedDate</code>

Note that we have a custom field in our MSMQL, which is perfectly legal. We know this because it ends in “__c”. The corresponding search object would be created like this:

<code>Search s = <span class="hljs-keyword">new</span> Search();
s.AddOutputColumn(<span class="hljs-string">"FirstName"</span>);
s.AddOutputColumn(<span class="hljs-string">"LastName"</span>);

s.AddCriteria(Expr.Equals(<span class="hljs-string">"Email"</span>, <span class="hljs-string">"gmail"</span>));

<span class="hljs-comment">// now, here's our parenthesis, which is it's own group</span>
SearchOperationGroup sog = <span class="hljs-keyword">new</span> SearchOperationGroup();
sog.Criteria.Add(Expr.IsGreaterThan(<span class="hljs-string">"Age"</span>, <span class="hljs-number">15</span>));
sog.Criteria.Add(Expr.Equals(<span class="hljs-string">"Gender__c"</span>, <span class="hljs-string">"Male"</span>));

s.AddCriteria(sog); <span class="hljs-comment">// add the parenthetical statement</span>

<span class="hljs-comment">// add the sort column</span>
s.AddSortColumn(<span class="hljs-string">"CreatedDate"</span>);
</code>

You can see this is more tedious that using a MSQL statement. But for instances where you need to build the search programmatically, you can.

Aggregate Search Functions

MemberSuite also let's you run aggregate searches. Consider this MSQL query:

<code><span class="hljs-keyword">Select</span> <span class="hljs-keyword">count</span>(FirstName), <span class="hljs-keyword">Avg</span>(Age), <span class="hljs-keyword">Min</span>(CreatedDate),<span class="hljs-keyword">Max</span>(LastModifiedDate) <span class="hljs-keyword">from</span> Individual <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> _Preferred_Address_State</code>

Not only is this perfectly good MSQL, it can be accomplished with the following search object:

<code>Search s = <span class="hljs-keyword">new</span> Search();
s.OutputColumns.Add(<span class="hljs-keyword">new</span> SearchOutputColumn { 
</code> <code>Name = <span class="hljs-string">"FirstName"</span>, AggregateFunction = SearchOuputAggregate.Count });
s.OutputColumns.Add(<span class="hljs-keyword">new</span> SearchOutputColumn { 
</code> <code>Name = <span class="hljs-string">"Age"</span>, AggregateFunction = SearchOuputAggregate.Average });
s.OutputColumns.Add(<span class="hljs-keyword">new</span> SearchOutputColumn { 
</code> <code>Name = <span class="hljs-string">"CreatedDate"</span>, AggregateFunction = SearchOuputAggregate.M<span class="hljs-keyword">in</span> });
s.OutputColumns.Add(new SearchOutputColumn { 
</code> <code>Name = <span class="hljs-string">"LastModifiedDate"</span>, AggregateFunction = SearchOuputAggregate.Max });

<span class="hljs-comment">// now, the group by</span>
s.OutputColumns.Add(<span class="hljs-keyword">new</span> SearchOutputColumn { 
</code> <code><span class="hljs-attr">Name</span> = <span class="hljs-string">"_Preferred_Address_State"</span>, AggregateFunction = SearchOuputAggregate.GroupBy });</code>

Just like in SQL, if you use an aggregate function, then all output columns must either be another aggregate or must be a GROUP BY column.

Search Operations

The following search expressions are supported:

  • Contains/DoesNotContain
  • ContainsOneOfTheFollowing/DoesNotContainOneOfTheFollowing
  • Equals/DoesNotEqual
  • IsBetween/IsNotBetween
  • IsBlank/IsNotBlank
  • IsGreaterThan
  • IsGreaterThanOrEqualTo
  • IsLessThan
  • IsLessThanorEqual

The following aggregate functions are supported:

  • None (Default)
  • GroupBy
  • Sum
  • Average (AVG)
  • Count
  • Min
  • Max

Search Specifications

Before we dive into these concepts, it's worth explaining that Search is a big deal within MemberSuite. By default, any field that is on a MemberSuiteObject, including custom fields, can be searched. However, searches very often expose extra fields that do not exist on objects. These are usually very useful calculations/aggregates that you should know about an take advantage of. The combination of these fields make up a Search Specification. Just like objects, searches can be described For example, consider an Invoice, which has the fields:

  • Total
  • BalanceDue
  • DueDate

The search specification adds these fields:

  • AmountPaid
  • Age

While these don't appear in the object themselves, you can see how they are useful calculations that you'd want to pull data based upon. For information about what fields exist in what searches, see the reference at the end of this manual.

Cross-Object Searches

A very powerful feature of searches is called cross-object searches. These allow you to search into object references; think of them like left outer joins in the SQL world. Consider the Invoice search, which has:

  • BillTo
  • Total

The BillTo field is actually a reference to an Entity. As per our aggregate rules, we'd expect an ID to come back in that field. But what if we need to know the name of all invoice owners who have invoices for more than $100? Simple:

<code><span class="hljs-keyword">Select</span> BillTo.Name <span class="hljs-keyword">from</span> Invoice <span class="hljs-keyword">where</span> Total><span class="hljs-number">100</span></code>

The dot(.) notation dereferences the object, allowing you to access it's properties. You have a depth limit of five (5) for this; building on the previous example, say we want to find all people who have invoices over $100, but we want to see what state their company is in:

<code><span class="hljs-selector-tag">Select</span> <span class="hljs-selector-tag">BillTo</span><span class="hljs-selector-class">.Name</span>, <span class="hljs-selector-tag">BillTo</span><span class="hljs-selector-class">.PrimaryOrganization</span><span class="hljs-selector-class">._PreferredAddress_State</span> <span class="hljs-selector-tag">from</span> <span class="hljs-selector-tag">Invoice</span> <span class="hljs-selector-tag">where</span> <span class="hljs-selector-tag">Total</span> > 100</code>

Whenever you see a field of type Reference this means you can perform a cross object search to drill down into it's properties.

Returning Objects

There may be time when you want to return whole objects, not just field values, for the search. An example is when you're trying to do a merge. You have an email, and you want to find the person who has that email, or create a new record if it doesn't exist. MemberSuite offers two syntaxes for this, depending on whether you want to find a single object, or multiple objects:

<code><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">OBJECT</span>() <span class="hljs-keyword">FROM</span> INDIVIDUAL <span class="hljs-keyword">WHERE</span> EMAIL=<span class="hljs-string">'andrew@gmail.com'</span>
<span class="hljs-keyword">SELECT</span> OBJECTS() <span class="hljs-keyword">FROM</span> INDIVIDUAL <span class="hljs-keyword">WHERE</span> EMAIL=<span class="hljs-string">'andrew@gmail.com'</span>
</code>

The first will only return a single object (or no object, if no such record exists), the second will return all matching records. It will use the ID field of the Individual to load the MemberSuiteObject and send it back via the API.

Consider a scenario in which you don't want to pull the object being searched on, but the related object. For instance, you wanted to pull a MemberSuiteObject for everyone who had an invoice >30 days. You could use the name of the field to use for the Object load:

<code><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">OBJECT</span>(BILLTO) <span class="hljs-keyword">FROM</span> INVOICE <span class="hljs-keyword">WHERE</span> AGE>30
<span class="hljs-keyword">SELECT</span> OBJECTS(BILLTO) <span class="hljs-keyword">FROM</span> INVOICE <span class="hljs-keyword">WHERE</span> AGE>30
</code>

Again, the first example will only pull a single object, where the second will pull all objects. In this case, instead of pulling the Invoice, you'd expect to get an Entity back.

Developer's Guide : API : The MemberSuite Object

Overview

In MemberSuite’s system, domain objects are core objects that contain properties, methods, etc. But when you integrate with MemberSuite via the API, you need to be able to get at objects in a language-agnostic, serializable way. For this, we utilize the MemberSuiteObject.

The MemberSuite object is an implementation of the Data Transfer Object design pattern. Instead of being a core in-memory object, it is a collection of name/value pairs that represent the object. The MemberSuiteObject is designed to be serializable, meaning that it can travel across the API boundary without a problem. Here again we see the usefulness of aggregates; the aggregate boundary can now be considered a whole MemberSuiteObject for the purposes of transport.

Whenever you request an object from the API, the API goes through and converts the response to a MemberSuiteObject via a process called serialization. The MemberSuiteObject gives us several benefits:

  • It’s name/value pair structure can easily travel across the Internet, and be consumed by any language
  • An association’s custom fields can be placed in the name/value pair collection, making the MemberSuite object a direct representation of the association’s object structure
  • Entirely new objects can be created/transported, like Custom Objects, because the MemberSuiteObject is not constrained to any in-memory object

That second point is important. Associations have the ability to create custom fields for their objects. Those will always come across as fields that end in __c. So, you can always tell a custom field by looking for keys in the MemberSuiteObject ending in "__c".

System Fields

Since every object in MemberSuite derives from Aggregate, there are some base fields that each object has. They are:

  • ID – this is the GUID identifier that was described in the previous article
  • Name – by rule, all objects in MemberSuite must have a name. This name need not necessarily be unique, but there must be a name
  • SystemTimestamp – this is a binary field that tracks each time a record was updated. When you save an object, the timestamp is compared with the one in the database, and if the record has changed, you’ll need to get a new copy before saving again

Other Common Fields

In addition to the system fields, here are fields you see frequently on objects:

  • LocalID – this is the numeric ID assigned to the object. This is unique to the association specifically. Think of this like a identity key in a traditional database
  • Code – Configuration objects often use a code in addition to a name. This makes it easy to reference the object by Code, so that if the Name changes, references to the object do not break

Object Description

Since each association can have their own custom fields, and even their own custom objects, there needs to be a way to determine what fields are available for an object. This process is called object description. This result of description is class metadata. Class metadata incudes field names, types, default values, visibilities, and a number of other properties.

You can call the DescribeObject API method to describe an object.

Strongly Typed MemberSuiteObjects

If you’re using the MemberSuite SDK, then you can access strongly typed versions of the MemberSuite object. These are a convenience and make it easier to code, so:

<code>MemberSuiteObject msoOrder = <span class="hljs-keyword">new</span> MemberSuiteObject();
msoOrder[<span class="hljs-string">"Total"</span>] = 100;
msoOrder[<span class="hljs-string">"Date"</span>] = DateTime.Today
</code>

Becomes:

<code>msOrder o = <span class="hljs-keyword">new</span> msOrder();
o.Total = 100;
o.Date = DateTime.Today;
</code>

msOrder derives from MemberSuiteObjects; the properties Type and Date simply make calls to the underlying field dictionary for MemberSuiteObject; thus, this is simply a convenience for you to use with IntelliSense enabled IDEs.

Developer's Guide : API : Objects in MemberSuite

Tenancy in MemberSuite

In MemberSuite, there is a difference between a Customer and an Association. A Customer is a billing entity – we have a contractual relationship with the organization represented by this record. An Association is actually a “tenant” in our system; it’s a repository of objects that comprise an association. One customer usually has one association, but there are exceptions to this, like when a Customer is in the business of managing multiple associations (Association Management Companies), or when sandbox associations are created under the customer.

Thus, the 99% of all objects in MemberSuite live in an association.

How Objects Work in MemberSuite

MemberSuite is inherently an object oriented design; we use object to encapsulate state and behavior. There is a special class of objects that correspond to the functionality of the software called domain objects. Domain objects borrow from Eric Evan’s Domain Driven Design – by definition, these objects should represent pieces of the domain that a domain expert (or subject matter expert) can understand. Consider the following:

We can see the concept of an Individual and an Organization are both types of Entities. An Entity can own an order – which means either an individual or an Organization can define an order.

These concepts are easily readable and verifiable by a non-programmer domain expert, which is the very point of domain objects. In essence, domain objects represent the key concepts of the problem an application is trying to solve.

MemberSuite has over 200 domain objects, spanning over 20 different modules, or namespaces. Examples of these objects:

  • Individual
  • Invoice
  • Order
  • Event
  • Competition

Object Identification

Each object has a globally unique identifier, or GUID. A GUID is a 32-byte value that is designed to be unique across all time and space. For more information on GUIDs, take a look at:

http://en.wikipedia.org/wiki/Globally_unique_ident...

The GUID ensures that we can locate an object across multiple databases, and across time and space. The GUID has a specific format that allows us, from a GUID, to determine the association that the object belongs to and the type of objects.

The first 4 bytes of the GUID are called the association hint. This identifies the association the object belongs to. As a rule, these 4 bytes match the 4 bytes of the target association. In this example, MemberSuite determines what association this object belongs to so by finding an association whose ID belongs with 594811f7.

The second two bytes are called the guid hint. This tells MemberSuite what type of object it is. In this case, 0006 indicates that the object is of type Individual. The Data Dictionary reference included in this documentation will list the 2-byte guid hint for each object.

Aggregates

Borrowing further from Domain Driven Design, we define an aggregate as an object boundary. There are several important reasons for this. Consider an object, Invoice. An invoice has multiple InvoiceLineItem objects. But invoices have rules; in particular, one invoice must have at least one line item. Additionally, line items must add up to the invoice total.

If you have the ability to modify InvoiceLineItem records directly, how would one enforce these rules? It would quickly become difficult, as you would have to write logic to check for other invoice line items when saving individual line items.

Conversely, we can make a rule for ourselves; you cannot save InvoiceLineItems directly, you must save them within the context of an Invoice. This solves our problem; since invoice line items can’t be referenced or saved directly, we can rely on the Invoice object to apply any validation rules necessary. Thus, the Invoice, and the associated InvoiceLineItems, form an aggregate boundary. The Invoice is known as an aggregate. The InvoiceLineItem is known as an aggregate child.

Aggregates have the following properties:

  1. All aggregates in MemberSuite have IDs that can be referenced directly
  2. Aggregates can contain non-aggregates, but cannot contain direct links to other aggregates. If they point to other aggregates (i.e., the Order.BillTo pointing to an Entity), they must reference the ID, instead of the whole object itself
  3. Aggregates can perform their own validations
  4. Aggregates must travel together across the API boundaries – so when you pull an Invoice, you must get its invoice line items.

AssociationDomainObjects vs. The Others

Now, consider this object model:

As you can see, the fundamental concept of MemberSuite is a DomainObject. From there, we have aggregates and aggregate children. Under aggregate, we’ve got objects that live in an association (remember that 99% of objects do), and then objects that live outside of an association.

Think of MemberSuite as your apartment building. You live in an apartment. So does your neighbor. So does his. Almost everyone lives in an apartment building, except for the super-intendent. The super has his own office that isn’t an apartment, but instead supports all of the other apartments.

In MemberSuite, each association is like an apartment. Almost everything lives in an association, or an apartment. A few things don’t – they live like the super, and in MemberSuite the super’s office is called the catalog. Catalog objects are global across associations and live in a special database called – you guessed it – the Catalog.

Examples of catalog objects:

  • Customer
  • User

Developer's Guide : API : Reverse SSO FAQs

What is it?

If instead of using an external login system for users and then jumping into the MemberSuite portal, you would like to have them log into MemberSuite and then jump to one or more external sites seamlessly, you would need to implement Reverse SSO. This works the same way as SSO, except that MemberSuite generates the SSO Token and then the external sites must implement a page which validates that token.

Who owns the credentials?

In this case, typically, MemberSuite would own the credentials and users would log in through our login page, however it would be possible to use regular SSO where another site owns the credentials and forwards the user through SSO to the MemberSuite portal. In this case, Reverse SSO could still be used to jump back to the originating site (if there was concern about session expiration) or to a completely different site.

How do I implement this?

There are 2 steps to this process:

Building the Receive SSO Token page

A sample “ReceiveSSOToken” page can be found in the C# sample code. This page requires many of the same prerequisites and setup as the regular SSO process (see section below) including Access Keys and Signing Certificates. This page simply validates that the Token passed to it is valid through a call to the MemberSuite API.

Note: This token is only valid for a very short amount of time. You would not be able to cache it and use it later. You would want to make sure to generate the proper login session for the new site immediately and then continue with the process.

Creating Reverse SSO Links

To test that the new “ReceiveSSOToken” works, you would need to add a link somewhere in the MemberSuite Portal site content. This could be in the Portal Skin, a generic Portal Link, or in any other content exposed in the site (like an Event description or Portal Text Override).

The format of the link href would need to be:

javascript:JumpToExternalLink('http://mysite.org/ReceiveSSOToken.aspx', 'http://google.com', true);

This is a custom JavaScript function that will post the URLs appropriately to the page that will generate the Portal Security Token and redirect to the Client’s page. The parameters are:

  1. The URL of the page the client has built to receive and process the token
  2. The URL of the page to pass as the “NextUrl” to the client page (for redirecting after login)
  3. “true” to open in a new tab or “false” to keep in the same tab\window

We also pass to the client page the current URL as the “ReturnUrl” in case the receiving page wants to provide a “return to MemberSuite” link that takes the user back to exactly where they were.

Optional Keep-Alive Process

Since you may now be jumping between these 2 websites, one problem you may encounter is that your session on the MemberSuite Portal may expire while the user is looking at pages on your other site. The MemberSuite Portal will expire the session after 20 minutes of inactivity.

The simplest way to handle linking out of MemberSuite would be to push the external link to a new tab\window in the browser. This would allow the MemberSuite portal to remain open and continue to manage its own session timeout. If, however, you prefer a more seamless transition between MemberSuite and non-MemberSuite pages all within the same tab\window, you would need to implement keep-alive pages. Effectively, what this means is that every time a MemberSuite portal page loads, it will dynamically load a URL on the non-MemberSuite site to keep that session from expiring and then any pages on the non-MemberSuite site would need to also dynamically load a URL on the MemberSuite site.

You would want to put something like this in the header or footer of your non-MemberSuite site HTML so that is loads with every page:

<div style="display: none">
    <iframe frameborder="0" height="0" src="https://site.membersuite.com/KeepAlive.aspx" width="0"></iframe>
</div>

You would also need to add something similar to you Portal Skin HMTL to connect to a similar page on your non-MemberSuite site.

Developer's Guide : API : Single Sign On

Overview

At this point you should have the following prerequisites as detailed in previous sections:

  • Access Key ID
  • Secret Access Key
  • Association ID
  • Signing Certificate ID
  • Signing Certificate private key in XML or X.509 Certificate format

Performing the Single Sign On entails the following steps:

  1. Construct a message header with your Access Key ID, Association ID, and Signature from your Secret Access Key
  2. Use your Signing Certificate to sign the Portal username of the person to log in.
  3. Execute the CreatePortalSecurityToken Concierge API method supplying the message header as well as the method parameters (Portal username to log in, your Signing Certificate ID, and Digital Signature of the Portal username created using your Signing Certificate)
  4. Execute a HTTP POST to the Login.aspx page in your Portal (https://[PortalURL]/Login.aspx). This POST should include the byte array received back from Step 3 as a Base64 encoded string in the variable “Token”.

Common Scenarios

A Single Sign On is usually designed in one of three styles:

  1. Using .NET and the MemberSuite SDK (recommended)
  2. Using SOAP proxy objects generated from the Concierge API WSDL. These proxy objects may be generated from tools in .NET (Web Reference using wsdl.exe / Service Reference using svcutil.exe) or frameworks for other platforms (NuSOAP, Zend, etc for PHP, Java, or other platforms).
  3. By manually constructing and POSTing a SOAP envelope. This option is completely platform independent and does not rely on any frameworks. This requires in depth understanding of SOAP/XML as you will be responsible for constructing and parsing properly formatted SOAP requests/responses.

As a companion to this document, three samples are available demonstrating each of these approaches. Although these samples are written in .NET, the concepts used in the WSDL and manual SOAP projects are transferrable to any other platform.

SSO With SDK

The SSO With SDK website demonstrates how to perform a Single Sign On when using .NET and the MemberSuite SDK. This is the recommended approach for Single Sign On.

In this scenario, the MemberSuite SDK will automatically create the required message headers and use your Secret Key to sign the message. You simply have to set your Access Key ID and Secret Access Key as demonstrated in the Global.asax.

The default behavior of the MemberSuite SDK is to store an Association ID and Session ID in ThreadStatic variables. This sample also demonstrates how to implement the IConciergeAPIAssociationIdProvider and IConciergeAPISessionIdProvider interfaces. These implementations are registered in the Global.asax and allow the MemberSuite SDK to read the Association ID from the web.config and read/store the web user’s Concierge API Session ID in the web user’s Session.

This sample also uses the helper methods in the CryptoManager class to simplify the process of creating a Digital Signature with a Signing Certificate. This sample uses a Signing Certificate private key downloaded in a XML file.

The RedirectToPortal.aspx page dynamically generates a HTML form. This page will be sent to the web client along with JavaScript that will automatically POST the form along with the Token generated from CreatePortalSecurityToken method to your Portal.

SSO With Web Reference (WSDL)

The SSO With Web Reference (WSDL) website demonstrates how to perform a Single Sign On using SOAP proxy objects generated from the Concierge API WSDL. In this case the proxy objects were generated using the Add Web Reference command (wsdl.exe). Although the proxy objects generated using other frameworks may be slightly different in structure, the concepts are the same. You must construct and populate a ConceirgeRequestHeader and pass it along with the call to CreatePortalSecurityToken.

This sample also demonstrates the process of creating a Digital Signature with a Signing Certificate. This sample uses a Signing Certificate private key downloaded in a XML file.

The RedirectToPortal.aspx page dynamically generates a HTML form. This page will be sent to the web client along with JavaScript that will automatically POST the form along with the Token generated from CreatePortalSecurityToken method to your Portal.

SSO With Manual SOAP Messages

The SSO With Manual SOAP Messages website demonstrates how to perform a Single Sign On when no framework to simplify SOAP communications is available. This style of interacting with the Concierge API is completely platform independent. It is recommended that you have in-depth knowledge of SOAP communication standards and best practices for this approach.

This sample uses no helper methods and demonstrates constructing a message signature, digital signature with a Signing Certificate, and constructing and POSTing a SOAP envelope manually. This sample uses a Signing Certificate private key downloaded in a XML file.

The RedirectToPortal.aspx page dynamically generates a HTML form. This page will be sent to the web client along with JavaScript that will automatically POST the form along with the Token generated from CreatePortalSecurityToken method to your Portal.

Sample SOAP Envelopes

Whichever approach you use to implement a Single Sign On, the primary action will be an exchange with the Concierge API. This exchange may be abstracted away with proxy objects or the MemberSuite SDK, however the structure will be the same. This exchange will consist of a SOAP request being sent to the Concierge API and a SOAP response being sent back. The response will contain the Token that must be sent to the Portal over a HTTP POST. The following is a sample of these SOAP messages.

Sample CreatePortalSecurityToken Request

<code><span class="hljs-tag"><<span class="hljs-name">s:Envelope</span> <span class="hljs-attr">xmlns:s</span>=<span class="hljs-string">"http://schemas.xmlsoap.org/soap/envelope/"</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">s:Header</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">Action</span>></span><a href="http://membersuite.com/contracts/IConciergeAPIService/CreatePortalSecurityToken">http://membersuite.com/contracts/IConciergeAPIServ...</a><span class="hljs-tag"></<span class="hljs-name">Action</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">To</span>></span><a href="https://api.membersuite.com">https://api.membersuite.com</a><span class="hljs-tag"></<span class="hljs-name">To</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">ConciergeRequestHeader</span> <span class="hljs-attr">xmlns:i</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://membersuite.com/schemas"</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">SessionId</span> <span class="hljs-attr">i:nil</span>=<span class="hljs-string">"true"</span> /></span>
      <span class="hljs-tag"><<span class="hljs-name">AccessKeyId</span>></span>AAAAAAAAAAAAAAAAAAAAAA<span class="hljs-tag"></<span class="hljs-name">AccessKeyId</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">AssociationId</span>></span>00000000-0000-0000-0000-000000000000<span class="hljs-tag"></<span class="hljs-name">AssociationId</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">Signature</span>></span>AAAAAAAAAAAAAAAAAAAAAAAAAAAA<span class="hljs-tag"></<span class="hljs-name">Signature</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">ConciergeRequestHeader</span>></span>
  <span class="hljs-tag"></<span class="hljs-name">s:Header</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">s:Body</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">CreatePortalSecurityToken</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://membersuite.com/contracts"</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">portalUserName</span>></span>portalusername<span class="hljs-tag"></<span class="hljs-name">portalUserName</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">signingCertificateId</span>></span>AAAAAAAAAAAAAAAAAAAAAA<span class="hljs-tag"></<span class="hljs-name">signingCertificateId</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">signature</span>></span>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<span class="hljs-tag"></<span class="hljs-name">signature</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">CreatePortalSecurityToken</span>></span>
  <span class="hljs-tag"></<span class="hljs-name">s:Body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">s:Envelope</span>></span>
</code>

Sample CreatePortalSecurityToken Response

<code><span class="hljs-tag"><<span class="hljs-name">s:Envelope</span> <span class="hljs-attr">xmlns:s</span>=<span class="hljs-string">"http://schemas.xmlsoap.org/soap/envelope/"</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">s:Header</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">ConciergeResponseHeader</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://membersuite.com/schemas"</span> <span class="hljs-attr">xmlns:i</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">SessionId</span>></span>00000000-0000-0000-0000-000000000000<span class="hljs-tag"></<span class="hljs-name">SessionId</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">ConciergeResponseHeader</span>></span>
  <span class="hljs-tag"></<span class="hljs-name">s:Header</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">s:Body</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">CreatePortalSecurityTokenResponse</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://membersuite.com/contracts"</span>></span>
      <span class="hljs-tag"><<span class="hljs-name">CreatePortalSecurityTokenResult</span> <span class="hljs-attr">xmlns:a</span>=<span class="hljs-string">"http://schemas.datacontract.org/2004/07/MemberSuite.SDK.Results"</span> <span class="hljs-attr">xmlns:i</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">a:Cacheable</span>></span>false<span class="hljs-tag"></<span class="hljs-name">a:Cacheable</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">a:Errors</span> <span class="hljs-attr">xmlns:b</span>=<span class="hljs-string">"http://schemas.datacontract.org/2004/07/MemberSuite.SDK.Concierge"</span> /></span>
        <span class="hljs-tag"><<span class="hljs-name">a:NotModified</span>></span>false<span class="hljs-tag"></<span class="hljs-name">a:NotModified</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">a:Success</span>></span>true<span class="hljs-tag"></<span class="hljs-name">a:Success</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">a:ResultValue</span>></span>
           ...
41<span class="hljs-tag"></<span class="hljs-name">a:ResultValue</span>></span>
      <span class="hljs-tag"></<span class="hljs-name">CreatePortalSecurityTokenResult</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">CreatePortalSecurityTokenResponse</span>></span>
  <span class="hljs-tag"></<span class="hljs-name">s:Body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">s:Envelope</span>></span></code>


Developer's Guide : API : Signing Certificates

Overview

Certain methods on the Concierge require you to verify your identity with a Digital Signature. This Digital Signature is required on sensitive methods such as the CreatePortalSecurityToken method used for Single Sign On. This signature is required in addition to the standard Secret Access Key message signature included in the header of all Concierge API method calls. Therefore, a client with an Access Key is authorized to execute Concierge API method calls but NOT authorized to login as other users unless they also have access to a registered Signing Certificate.

There are two ways to register a new Signing Certificate: using an existing X.509 Certificate, or allowing the Concierge API to generate a new public/private key pair. In both scenarios, the client code must have access to the private key while the public key ONLY is saved in MemberSuite.

Once the Signing Certificate is registered, the private key can be used to generate a unique signature of some data. This data, the Signing Certificate ID, and the signature are then passed to MemberSuite where the Concierge API can use the public key to verify that the signature was generated by a signer with access to the paired private key.

You are responsible to maintain the security of your private key. Additionally, you should not allow users to submit data to be signed until their credentials/identity has been verified.

Creating/Using a Signing Certificate

The simplest way to create a Signing Certificate is to allow MemberSuite to generate a RSA-SHA1 key pair. MemberSuite will then allow you to download an XML document containing the private key and will automatically store the paired public key.

Once you have saved the XML file with the private key, it can be used to construct a new RSA-SHA1 signer. This signer can then be used to generate unique digital signatures. The following code snippet demonstrates this scenario:

<code><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">byte</span>[] <span class="hljs-title">GenerateDigitalSignature</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> dataToSign</span>)
</span>{
   <span class="hljs-comment">//This is an XML file so use Unicode encoding</span>
   <span class="hljs-keyword">string</span> xmlKeyString = File.ReadAllText(<span class="hljs-string">"signingcertificate.xml"</span>, Encoding.Unicode);

   <span class="hljs-comment">//Create a new signer using RSA and a SHA1 hash using the private key</span>
   RSACryptoServiceProvider signer = <span class="hljs-keyword">new</span> RSACryptoServiceProvider();
   signer.FromXmlString(xmlKeyString);
 
   <span class="hljs-keyword">return</span> signer.SignData(Encoding.ASCII.GetBytes(dataToSign), <span class="hljs-keyword">new</span> SHA1CryptoServiceProvider());
}
</code>

PHP and other platforms have various options for a HMAC-SHA1 implementation. In order to test your algorithms, use the following:

Sample SigningCertificate.XML

<code><span class="hljs-tag"><<span class="hljs-name">RSAKeyValue</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">Modulus</span>></span>ze3EJPfwdYAIvTlroR9FxZxHcRKc9SbU+gtFmy3dqsJPB85mxSab31RPG8kbSZ90wya9CVIa4z5MJE2teq7J5lM9nLQ9iHiASQ6YMYLZj21ksN1xWXTChmIkDdm8kLGCRN6f1M0eUlITYGW8+CYmQzsC0eIB8YmaF3/ZmJrU9JM=<span class="hljs-tag"></<span class="hljs-name">Modulus</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">Exponent</span>></span>AQAB<span class="hljs-tag"></<span class="hljs-name">Exponent</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">P</span>></span>+aWaWTxJLNksC5RpkAtrTC+NQFR3PZh1WykG9UkN7enZ/DAZtoSBIwm4dHbV7tGpaL7vt970G12J5xSeslj3yw==<span class="hljs-tag"></<span class="hljs-name">P</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">Q</span>></span>0ytZVAFLlJKdMzXojYsxRQGs3ejr/sIWjDkEvi3NpX6ZKbzQn8E5Pak7qfrpQEquSmCxLF3CIokcPnDrZsSNWQ==<span class="hljs-tag"></<span class="hljs-name">Q</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">DP</span>></span>ez2eeckCEsrPLJRCnKKWgJDE+Wn5R4YZATy6u6Ip5zZXr2CLgQfevE5TKeN0byY/rH791laRSWUe693JDiBPiw==<span class="hljs-tag"></<span class="hljs-name">DP</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">DQ</span>></span>V231OCCpKERjrZY+SIw+w/FjnyUWwI2fREM/QXY5VLHLvEoenmYjyvHMcB4ggKvq6YSLFnFjNWVLOlcKP6xVUQ==<span class="hljs-tag"></<span class="hljs-name">DQ</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">InverseQ</span>></span>JDPOSPR8lhnTHnBSdIF1EOjDq60c+5BhOiGfREfSDUva9km7g5m0HFQK3uhftHZlNKOG9DEDNwN9HyttMXG97A==<span class="hljs-tag"></<span class="hljs-name">InverseQ</span>></span>
  <span class="hljs-tag"><<span class="hljs-name">D</span>></span>BbJfK7qkL0v0ce+lt5Za4sWZAFrIdsu1NpmbOljBehjYANpH44pW4428hoNAuv+rRj7LGffogILXrmVruCofsa+6T86+w+o7qfGAu35cpMVMsytE77nNvpMozNBsOz9y7WdTo8LX6RQnW7d5MEE9SAPBWDic/S/x7d/W1XPnNDE=<span class="hljs-tag"></<span class="hljs-name">D</span>></span>
<span class="hljs-tag"></<span class="hljs-name">RSAKeyValue</span>></span></code>

Portal Username: test@membersuite.com”

The Base64 encoded signature should be:

<code>jTDKoH+INi19kGWn7WRk/PZegLv/9fPUOluaM57x8y1tkuwxOiyX86gxsZ7gU/OsStIT9Q5SVSG5NoaL3B+AxjuLY8b7XBMfTXHv2vidrDkkTTBW0D2LsrkZ3xzmvvPqqfA3tF2HXUYF+zoiTsr3bQdA32CJ+lDNkf+QjV3ZEoc=</code>

An automatically generated Signing Certificate in the Console can be created with the following steps:

  1. Click on Customer Center in the Console Toolbar
  2. Click on Manage Users
  3. Click on the View icon for the User that will own the Signing Certificate
  4. Click Create a Signing Certificate
  5. Click Generate Key Pair

Click Download Key and save the private key for your new Signing Certificate and make a note of your new Signing Certificate ID

Creating/Using a Signing Certificate from a X.509 Certificate

Alternatively, if you have an X.509 certificate with a RSA-SHA1 key pair you can use it to create a Signing Certificate by registering the public key of the certificate. You can either cut & paste the contents of the certificate into the MemberSuite Console OR you can upload a .cer certificate file and MemberSuite will extract the public key only. If you are using a RSA-SHA1 key pair generated by MemberSuite as described above, you can skip this section.

If you are using the MemberSuite SDK, the following code snippet demonstrates how you can use a X.509 certificate from your Windows certificate store to generate a Digital Signature for the string data in the variable “data”. This snippet assumes there is a certificate with the subject “CN=SampleCertificate” in the Local Machine Personal certificate store. This code must be executed by a user with access to the Local Machine certificate store.

<code><strong><span class="hljs-attribute">MemberSuite</span>.SDK.Utilities.CryptoManager.Sign(data, <span class="hljs-string">"CN=SampleCertificate"</span>, <span class="hljs-string">""</span>);</strong></code>

If you are NOT using the MemberSuite SDK, the following code snippet describes the same behavior as the MemberSuite.SDK.Utilities.CryptoManager.Sign method.

/// <summary>
/// Welcome to MemberSuite API Samples. You can run this console app and be presented with a list of samples
/// in this assembly. Selecting the appropriate menu item will run the selected sample.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
    string data = "datatosign";
    X509Certificate2 sampleCertificate = GetCertificateByStoreAndSubject("CN=SampleCertificate", "");
    byte[] signature = Sign(Encoding.ASCII.GetBytes(data), sampleCertificate.PrivateKey);
}
public static X509Certificate2 GetCertificateByStoreAndSubject(string subject, string store)
{
    //Determine the store to use - always LocalMachine but the store name may be configured
    X509Store certStore = string.IsNullOrWhiteSpace(store)
    ? new X509Store(StoreLocation.LocalMachine)
    : new X509Store(store, StoreLocation.LocalMachine);
    certStore.Open(OpenFlags.ReadOnly);
    //Not a keyed collection so query on the subject name using LINQ
    X509Certificate2 result =
    certStore.Certificates.Cast<X509Certificate2>().FirstOrDefault(
    currentCert => currentCert.Subject.Equals(subject));
    if (result == null)
        throw new ApplicationException(
        String.Format("Unable to locate certificate with Container Name '{0}' in Store {1}", subject, certStore.Name));
    return result;
}
public static byte[] Sign(byte[] data, AsymmetricAlgorithm key)
{
    if (data == null || data.Length == 0)
        throw new ArgumentNullException("data", "The data to sign is required.");
    if (key == null)
        throw new ArgumentNullException("data", "The key to use to sign the data is required.");
    RSACryptoServiceProvider RSASigner = key as RSACryptoServiceProvider;
    if (RSASigner != null)
        //Currently only supports the certificate default SHA1
        return RSASigner.SignData(data, new SHA1CryptoServiceProvider());
    throw new ApplicationException("Unsupported key algorithm. Only RSA is supported.");
}

Developer's Guide : API : How To: Locate Your Association ID

You must specify your Association ID in the header of each request to the Concierge API. You can determine your Association ID by logging into the console and navigating to the Association Settings screen.

1. Click on Setup in the Console toolbar

2. Click on Association Settings

3. Make a note of your specific Association’s ID

Developer's Guide : API : How To: Create a New Access Key

You can create a new Access Key by logging into the MemberSuite Console and executing the following steps:

1. Click on Customer Center in the Console Toolbar.

2. Click on Manage Users.

3. Click on the View icon for the User who will own the Access Key

4. Click on Create an Access Key

5. The new Access Key is available to cut & paste on this screen. Optionally, you can download the Access Key ID and Secret Access Key in .CSV format