| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| === Recipes |
| |
| ==== Introduction |
| |
| In this section of the documentation we provide quick recipes focused on helping you achieve a specific result with |
| Apache Unomi. |
| |
| ==== How to read a profile |
| |
| The simplest way to retrieve profile data for the current profile is to simply send a request to the /context.json |
| endpoint. However you will need to send a body along with that request. Here's an example: |
| |
| Here is an example that will retrieve all the session and profile properties. |
| |
| [source] |
| ---- |
| curl -X POST http://localhost:8181/context.json?sessionId=1234 \ |
| -H "Content-Type: application/json" \ |
| -d @- <<'EOF' |
| { |
| "source": { |
| "itemId":"homepage", |
| "itemType":"page", |
| "scope":"example" |
| }, |
| "requiredProfileProperties":["*"], |
| "requiredSessionProperties":["*"], |
| "requireSegments":true |
| } |
| EOF |
| ---- |
| |
| The `requiredProfileProperties` and `requiredSessionProperties` are properties that take an array of property names |
| that should be retrieved. In this case we use the wildcard character '*' to say we want to retrieve all the available |
| properties. The structure of the JSON object that you should send is a JSON-serialized version of the http://unomi.apache.org/unomi-api/apidocs/org/apache/unomi/api/ContextRequest.html[ContextRequest] |
| Java class. |
| |
| Note that it is also possible to access a profile's data through the /cxs/profiles/ endpoint but that really should be |
| reserved to administrative purposes. All public accesses should always use the /context.json endpoint for consistency |
| and security. |
| |
| ==== How to update a profile from the public internet |
| |
| Before we get into how to update a profile directly from a request coming from the public internet, we'll quickly talk |
| first about how NOT to do it, because we often see users using the following anti-patterns. |
| |
| ===== How NOT to update a profile from the public internet |
| |
| Please avoid using the /cxs/profile endpoint. This endpoint was initially the only way to update a profile but it has |
| multiple issues: |
| |
| - it requires authenticated access. The temptation can be great to use this endpoint because it is simple to access |
| but the risk is that developers might include the credentials to access it in non-secure parts of code such as |
| client-side code. Since there is no difference between this endpoint and any other administration-focused endpoints, |
| attackers could easily re-use stolen credentials to wreak havock on the whole platform. |
| - No history of profile modifications is kept: this can be a problem for multiple reasons: you might want to keep an |
| trail of profile modifications, or even a history of profile values in case you want to understand how a profile |
| property was modified. |
| - Even when protected using some kind of proxy, potentially the whole profile properties might be modified, including |
| ones that you might not want to be overriden. |
| |
| ===== Recommended ways to update a profile |
| |
| Instead you can use the following solutions to update profiles: |
| |
| - (Preferred) Use you own custom event(s) to send data you want to be inserted in a profile, and use rules to map the |
| event data to the profile. This is simpler than it sounds, as usually all it requires is setting up a simple rule and |
| you're ready to update profiles using events. This is also the safest way to update a profile because if you design your |
| events to be as specific as possible to your needs, only the data that you specified will be copied to the profile, |
| making sure that even in the case an attacker tries to send more data using your custom event it will simply be ignored. |
| |
| - Use the protected built-in "updateProperties" event. This event is designed to be used for administrative purposes |
| only. Again, prefer the custom events solution because as this is a protected event it will require sending the Unomi |
| key as a request header, and as Unomi only supports a single key for the moment it could be problematic if the key is |
| intercepted. But at least by using an event you will get the benefits of auditing and historical property modification |
| tracing. |
| |
| Let's go into more detail about the preferred way to update a profile. Let's consider the following example of a rule: |
| |
| [source] |
| ---- |
| curl -X POST http://localhost:8181/cxs/rules \ |
| --user karaf:karaf \ |
| -H "Content-Type: application/json" \ |
| -d @- <<'EOF' |
| { |
| "metadata": { |
| "id": "setContactInfo", |
| "name": "Copy the received contact info to the current profile", |
| "description": "Copies the contact info received in a custom event called 'contactInfoSubmitted' to the current profile" |
| }, |
| "raiseEventOnlyOnceForSession": false, |
| "condition": { |
| "type": "eventTypeCondition", |
| "parameterValues": { |
| "eventTypeId": "contactInfoSubmitted" |
| } |
| }, |
| "actions": [ |
| { |
| "type": "setPropertyAction", |
| "parameterValues": { |
| "setPropertyName": "properties(firstName)", |
| "setPropertyValue": "eventProperty::properties(firstName)", |
| "setPropertyStrategy": "alwaysSet" |
| } |
| }, |
| { |
| "type": "setPropertyAction", |
| "parameterValues": { |
| "setPropertyName": "properties(lastName)", |
| "setPropertyValue": "eventProperty::properties(lastName)", |
| "setPropertyStrategy": "alwaysSet" |
| } |
| }, |
| { |
| "type": "setPropertyAction", |
| "parameterValues": { |
| "setPropertyName": "properties(email)", |
| "setPropertyValue": "eventProperty::properties(email)", |
| "setPropertyStrategy": "alwaysSet" |
| } |
| } |
| ] |
| } |
| EOF |
| ---- |
| |
| What this rule does is that it listen for a custom event (events don't need any registration, you can simply start |
| sending them to Apache Unomi whenever you like) of type 'contactInfoSubmitted' and it will search for properties called |
| 'firstName', 'lastName' and 'email' and copy them over to the profile with corresponding property names. You could of |
| course change any of the property names to find your needs. For example you might want to prefix the profile properties |
| with the source of the event, such as 'mobileApp:firstName'. |
| |
| You could then simply send the `contactInfoSubmitted` event using a request similar to this one: |
| |
| [source] |
| ---- |
| curl -X POST http://localhost:8181/eventcollector \ |
| -H "Content-Type: application/json" \ |
| -d @- <<'EOF' |
| { |
| "sessionId" : "1234", |
| "events":[ |
| { |
| "eventType":"contactInfoSubmitted", |
| "scope": "example", |
| "source":{ |
| "itemType": "site", |
| "scope":"example", |
| "itemId": "mysite" |
| }, |
| "target":{ |
| "itemType":"form", |
| "scope":"example", |
| "itemId":"contactForm" |
| }, |
| "properties" : { |
| "firstName" : "John", |
| "lastName" : "Doe", |
| "email" : "john.doe@acme.com" |
| } |
| } |
| ] |
| } |
| EOF |
| ---- |
| |
| |
| ==== How to search for profile events |
| |
| Sometimes you want to retrieve events for a known profile. You will need to provide a query in the body of the request |
| that looks something like this (and https://unomi.apache.org/rest-api-doc/#1768188821[documentation is available in the REST API]) : |
| |
| [source] |
| ---- |
| curl -X POST http://localhost:8181/cxs/events/search \ |
| --user karaf:karaf \ |
| -H "Content-Type: application/json" \ |
| -d @- <<'EOF' |
| { "offset" : 0, |
| "limit" : 20, |
| "condition" : { |
| "type": "eventPropertyCondition", |
| "parameterValues" : { |
| "propertyName" : "profileId", |
| "comparisonOperator" : "equals", |
| "propertyValue" : "PROFILE_ID" |
| } |
| } |
| } |
| EOF |
| ---- |
| |
| where PROFILE_ID is a profile identifier. This will indeed retrieve all the events for a given profile. |
| |
| ==== How to create a new rule |
| |
| There are basically two ways to create a new rule : |
| |
| - Using the REST API |
| - Packaging it as a predefined rule in a plugin |
| |
| In both cases the JSON structure for the rule will be exactly the same, and in most scenarios it will be more |
| interesting to use the REST API to create and manipulate rules, as they don't require any development or deployments |
| on the Apache Unomi server. |
| |
| [source] |
| ---- |
| curl -X POST http://localhost:8181/cxs/rules \ |
| --user karaf:karaf \ |
| -H "Content-Type: application/json" \ |
| -d @- <<'EOF' |
| { |
| "metadata": { |
| "id": "exampleEventCopy", |
| "name": "Example Copy Event to Profile", |
| "description": "Copy event properties to profile properties" |
| }, |
| "condition": { |
| "type": "eventTypeCondition", |
| "parameterValues": { |
| "eventTypeId" : "myEvent" |
| } |
| }, |
| "actions": [ |
| { |
| "parameterValues": { |
| }, |
| "type": "allEventToProfilePropertiesAction" |
| } |
| ] |
| } |
| EOF |
| ---- |
| |
| The above rule will be executed if the incoming event is of type `myEvent` and will simply copy all the properties |
| contained in the event to the current profile. |
| |
| ==== How to search for profiles |
| |
| In order to search for profiles you will have to use the /cxs/profiles/search endpoint that requires a Query JSON |
| structure. Here's an example of a profile search with a Query object: |
| |
| [source] |
| ---- |
| curl -X POST http://localhost:8181/cxs/profiles/search \ |
| --user karaf:karaf \ |
| -H "Content-Type: application/json" \ |
| -d @- <<'EOF' |
| { |
| "text" : "unomi", |
| "offset" : 0, |
| "limit" : 10, |
| "sortby" : "properties.lastName:asc,properties.firstName:desc", |
| "condition" : { |
| "type" : "booleanCondition", |
| "parameterValues" : { |
| "operator" : "and", |
| "subConditions" : [ |
| { |
| "type": "profilePropertyCondition", |
| "parameterValues": { |
| "propertyName": "properties.leadAssignedTo", |
| "comparisonOperator": "exists" |
| } |
| }, |
| { |
| "type": "profilePropertyCondition", |
| "parameterValues": { |
| "propertyName": "properties.lastName", |
| "comparisonOperator": "exists" |
| } |
| } |
| ] |
| } |
| } |
| } |
| EOF |
| ---- |
| |
| In the above example, you search for all the profiles that have the `leadAssignedTo` and `lastName` properties and that |
| have the `unomi` value anywhere in their profile property values. You are also specifying that you only want 10 results |
| beginning at offset 0. The results will be also sorted in alphabetical order for the `lastName` property value, and then |
| by reverse alphabetical order for the `firstName` property value. |
| |
| As you can see, queries can be quite complex. Please remember that the more complex the more resources it will consume |
| on the server and potentially this could affect performance. |
| |
| ==== Getting / updating consents |
| |
| You can find information on how to retrieve or create/update consents in the <<Consent API>> section. |
| |
| ==== How to send a login event to Unomi |
| |
| Tracking logins must be done carefully with Unomi. A login event is considered a "privileged" event and therefore for |
| not be initiated from the public internet. Ideally user authentication should always be validated by a trusted third- |
| party even if it is a well-known social platform such as Facebook or Twitter. Basically what should NEVER be done: |
| |
| 1. Login to a social platform |
| 2. Call back to the originating page |
| 3. Send a login event to Unomi from the page originating the login in step 1 |
| |
| The problem with this, is that any attacker could simply directly call step 3 without any kind of security. Instead the |
| flow should look something like this: |
| |
| 1. Login to a social platform |
| 2. Call back to a special secured system that performs an server-to-server call to send the login event to Apache |
| Unomi using the Unomi key. |
| |
| For simplicity reasons, in our login example, the first method is used, but it really should never be done like this |
| in production because of the aforementioned security issues. The second method, although a little more involved, is |
| much preferred. |
| |
| When sending a login event, you can setup a rule that can check a profile property to see if profiles can be merged on an |
| universal identifier such as an email address. |
| |
| In our login sample we provide an example of such a rule. You can find it here: |
| |
| https://github.com/apache/unomi/blob/master/samples/login-integration/src/main/resources/META-INF/cxs/rules/exampleLogin.json |
| |
| As you can see in this rule, we call an action called : |
| |
| mergeProfilesOnPropertyAction |
| |
| with as a parameter value the name of the property on which to perform the merge (the email). What this means is that |
| upon successful login using an email, Unomi will look for other profiles that have the same email and merge them into |
| a single profile. Because of the merge, this should only be done for authenticated profiles, otherwise this could be a |
| security issue since it could be a way to load data from other profiles by merging their data ! |
| |