Search

Clear filter
Article
Daniel Kutac · May 3, 2017

InterSystems IRIS Open Authorization Framework (OAuth 2.0) implementation - part 3

Created by Daniel Kutac, Sales Engineer, InterSystems Part 3. AppendixInterSystems IRIS OAUTH classes explainedIn the previous part of our series we have learned about configuring InterSystems IRIS to act as an OAUTH client as well as authorization and authentication server (by means of OpenID Connect). In this final part of our series we are going to describe classes implementing InterSystems IRIS OAuth 2.0 framework. We will also discuss use cases for selected methods of API classes.The API classes implementing OAuth 2.0 can be separated into three different groups according to their purpose. All classes are implemented in %SYS namespace. Some of them are public (via % package), some not and should not be called by developers directly.Internal classesThese classes belong to the OAuth2 package.Following table lists some classes of interest (for full list of classes please refer to online Class Reference of your Caché instance.) None of these classes should be used directly by application developers except those listed below table.Class nameDescriptionOAuth2.AccessTokenPersistentOAuth2.AccessToken stores an OAuth 2.0 access token and its related information. This is an OAUTH client copy of the access token.OAuth2.AccessToken is indexed by the combination of SessionId and ApplicationName. Therefore, only one scope may be requested for each SessionId/ApplicationName. If a second request is made with a different scope and access token has yet been granted, the scope in the new request becomes the expected scope.OAuth2.ClientPersistentThe OAuth2.Application class describes an OAuth2 client and references the Authorization server that it uses to authorize the application based on RFC 6749. A client system may be used with multiple authorization servers for different applications.OAuth2.ResponseCSP pageThis is the landing page for responses from an OAuth 2.0 authorization server used from InterSystems IRIS OAuth 2.0 client code. The response is processed here and then redirected to the eventual target.OAuth2.ServerDefinitionPersistentStores authorization server information used by an OAUTH client (this InterSystems IRIS instance). There can be defined multiple client configurations for each authorization server definition.OAuth2.Server.AccessTokenPersistentAccess tokens are managed by the OAuth2.Server.AccessToken at the OAUTH server. The class stores the access token and related properties. This class is also the means of communication between the various parts of the authorization server.OAuth2.Server.AuthCSP pageThe authorization server supports the authorization control flow for the Authorization Code and Implicit grant types as specified in RFC 6749. The OAuth2.Server.Auth class is a subclass of %CSP.Page which acts as the Authorization Endpoint and controls the flow in accordance with RFC 6749.OAuth2.Server.ClientPersistentOAuth2.Server.Configuration is a persistent class which describes the clients which have registered with this authorization server.OAuth2.Server.ConfigurationPersistentStores authorization server configuration. All configuration classes have corresponding System Management Portal page where users fill-in configuration details.OAuth2.Client, OAuth2.ServerDefinition, OAuth2.Server.Client and OAuth2.Configuration objects may be opened, modified and saved to create or modify configurations without using the UI. You can use these classes to manipulate configurations programmatically.Server customization classesThese classes belong to %OAuth2 package. The package contains a set of internal classes – utilities, we only describe those classes that can be used by developers. These classes are referred to in OAuth 2.0 Server Configuration page%OAuth2.Server.AuthenticateCSP Page%OAuth2.Server.Authenticate acts as the subclass for all user written Authenticate classes as well as the default Authenticate class. The Authenticate class is used by Authorization Endpoint at OAuth2.Server.Auth to authenticate the user. This class allows the customization of the authentication process.The following methods may be implemented to override the default in OAuth2.Server:· DirectLogin – use only when not willing to display login page· DisplayLogin – implements authorization server login form· DisplayPermissions – implements form with a list of requested scopesFurther customization of look & feel can be done via modifying CSS. The CSS styles are defined in DrawStyle method.loginForm is for DisplayLogin formpermissionForm is for DisplayPermissions form%OAuth2.Server.ValidateCSP PageThis is the default Validate User Class which is included with the server. The default class will use the user database of the Cache instance where the authorization server is located to validate the user. The supported properties will be issuer (Issuer), roles and sub (Username).The Validate User Class is specified in the Authorization Server Configuration. It must contain a ValidateUser method which will validate a username/password combination and return a set of properties associated with this user.%OAuth2.Server.GenerateRegistered objectThe %OAuth2.Server.Generate is the default Generate Token Class which is included with the server. The default class will generate a random string as the opaque access token.The Generate Token Class is specified in the Authorization Server Configuration. It must contain a GenerateAccessToken method that will be used to generate an access token based on the array of properties that is returned by the ValidateUser method.%OAuth2.Server.JWTRegistered objectThe %OAuth2.Server.JWT is the Generate Token Class which creates a JSON Web Token which is included with the server. The Generate Token Class is specified in the Authorization Server Configuration. It must contain a GenerateAccessToken method that will be used to generate an access token based on the array of properties that is returned by the ValidateUser method.%OAuth2.UtilsRegistered objectThis class implements, among others, logging of various entities. A sample code in Customization chapter shows possible use.Following image shows corresponding section of the OAuth 2.0 authorization server configurationIn case you are going to use OpenID Connect with JWT formatted identity token (id_token), please replace default Generate Token Class %OAuth2.Server.Generate with %OAuth2.Server.JWT in the configuration, otherwise leave default Generate class.We will discuss customization options in more details in a separate chapter later.Public API classesPublic API classes are used by application developers to provide correct values for their web application message flow as well as perform access token validation, introspection and so on.These classes are implemented in %SYS.OAuth2 package. The table lists some of implemented classes.%SYS.OAuth2.AccessTokenRegistered objectThe %SYS.OAuth2.AccessToken class defines the client operations which allow an access token to be used to authorize to a resource server. The underlying token is stored in OAuth2.AccessToken in the CACHESYS database. OAuth2.AccessToken is indexed by the combination of SessionId and ApplicationName. Therefore, only one scope may be requested for each SessionId/ApplicationName. If a second request is made with a different scope and access token has yet been granted, the scope in the new request becomes the expected scope.%SYS.OAuth2.AuthorizationRegistered objectThe %SYS.OAuth2.Authorization class contains the operations which are used to authorize a client by obtaining an access token. The underlying token is stored in OAuth2.AccessToken in the CACHESYS database. OAuth2.AccessToken is indexed by the combination of SessionId and ApplicationName. Therefore, only one scope may be requested for each SessionId/ApplicationName. If a second request is made with a different scope and access token has yet been granted, the scope in the new request becomes the expected scope.Note that this class is in CACHELIB and thus available everywhere. However, token storage is in CACHESYS and thus not directly available to most code.%SYS.OAuth2.ValidationRegistered objectThe %SYS.OAuth2.Validation class defines the methods used to validate (or invalidate) an access token. The underlying token is stored in OAuth2.AccessToken in the CACHESYS database. OAuth2.AccessToken is indexed by the combination of SessionId and ApplicationName. Therefore, only one scope may be requested for each SessionId/ApplicationName. If a second request is made with a different scope and access token has yet been granted, the scope in the new request becomes the expected scope.Let’s have a look at some methods and classes from this group closely.Every client application class, that uses access token, MUST check its validity. This is done somewhere in the OnPage method (or corresponding method in ZEN or ZENMojo page).This is the code snippet: // Check if we have an access token from oauth2 server set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1, scope2",.accessToken,.idtoken,.responseProperties,.error) // Continue with further checks if an access token exists. // Below are all possible tests and may not be needed in all cases. // The JSON object which is returned for each test is just displayed. if isAuthorized { // do whatever – call resource server API to retrieve data of interest } Every time we call API of the resource server, we need to supply access token. This is done by AddAccessToken method of %SYS.OAuth2.AccessToken class, see code snippet here set httpRequest=##class(%Net.HttpRequest).%New() // AddAccessToken adds the current access token to the request. set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken( httpRequest,, ..#SSLCONFIG, ..#OAUTH2APPNAME) if $$$ISOK(sc) { set sc=httpRequest.Get(.. Service API url …) } In the sample code provided in previous parts of our series, we could see this code in OnPreHTTP method of the first application page (Cache1N). This is the best place to perform access token check for the application’s initial page. ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] { set scope="openid profile scope1 scope2" #dim %response as %CSP.Response if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,, scope,.accessToken,.idtoken,.responseProperties,.error) { set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls" } quit 1 } The IsAuthorized method of SYS.OAuth2.AccessToken class in the above code is checking whether valid access token exists and if not, lets us display the page content with login button/link pointing to the authentication form of the authorization server, otherwise redirects us right to the second page that actually does the work of retrieving the data. We can, however, change the code so it reads this: ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] { set scope="openid profile scope1 scope2" set sc=##class(%SYS.OAuth2.Authorization).GetAccessTokenAuthorizationCode( ..#OAUTH2APPNAME,scope,..#OAUTH2CLIENTREDIRECTURI,.properties) quit +sc } This variant has different effect. When using GetAccessTokenAuthorizationCode method of %SYS.OAuth2.Authorization class, we navigate directly to the authentication form of the authorization server, without displaying content of our application’s first page. This can be handy in cases, where the web application is invoked from a mobile device native application, where some user information has already been shown by the native application (the launcher) and there is no need to display web page with a button pointing to the authorization server. If you use signed JWT token, then you need to validate its content. This is done by the following method: set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(applicationName,accessToken,scope,,.jsonObject,.securityParameters,.sc) Detailed description of method parameters can be found at Class Reference documentation. Customization Let’s spend some time describing, what options OAUTH offers for Authentication / Authorization UI customization. Suppose your company policy demands some more restrictive behavior of scope granting. For example, you may run home banking application that connects to various banking systems within your bank. The bank only grants access to scope that contains information about the actual bank account being retrieved. As bank runs millions of accounts, it is impossible to define static scope for every single account. Instead, you can generate scope on the fly – during authorization processing, as part of custom authorization page code. For the purpose of the demo, we need to add one more scope to the server configuration – see image. We also added reference to custom Authenticate class, named %OAuth2.Server.Authenticate.Bank. So, how does the bank authentication class looks like? Here is possible variant of the class. It enhances standard authentication and authorization forms with user provided data. The information that flows between BeforeAuthenticate, DisplayPermissions and AfterAuthenticate methods is passed by properties variable of %OAuth2.Server.Properties class Class %OAuth2.Server.Authenticate.Bank Extends %OAuth2.Server.Authenticate { /// Add CUSTOM BESTBANK support for account scope. ClassMethod BeforeAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status { // If launch scope not specified, then nothing to do If 'scope.IsDefined("account") Quit $$$OK // Get the launch context from the launch query parameter. Set tContext=properties.RequestProperties.GetAt("accno") // If no context, then nothing to do If tContext="" Quit $$$OK try { // Now the BestBank context should be queried. Set tBankAccountNumber=tContext // Add scope for accno. -> dynamically modify scope (no account:<accno> scope exists in the server configuration) // This particular scope is used to allow the same accno to be accessed via account // if it was previously selected by account or account:accno when using cookie support Do scope.SetAt("Access data for account "_tBankAccountNumber,"account:"_tBankAccountNumber) // We no longer need the account scope, since it has been processed. // This will prevent existence of account scope from forcing call of DisplayPermissions. Do scope.RemoveAt("account") // Add the accno property which AfterAuthenticate will turn into a response property Do properties.CustomProperties.SetAt(tBankAccountNumber,"account_number") } catch (e) { s ^dk("err",$i(^dk("err")))=e.DisplayString() } Quit $$$OK } /// Add CUSTOM BESTBANK support for account scope. /// If account_number custom property was added by either BeforeAuthenticate (account) /// or DisplayPermissions (account:accno), then add the needed response property. ClassMethod AfterAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status { // There is nothing to do here unless account_number (account) or accno (account:accno) property exists try { // example of custom logging If $$$SysLogLevel>=3 { Do ##class(%OAuth2.Utils).LogServerScope("log ScopeArray-CUSTOM BESTBANK",%token) } If properties.CustomProperties.GetAt("account_number")'="" { // Add the accno query parameter to the response. Do properties.ResponseProperties.SetAt(properties.CustomProperties.GetAt("account_number"),"accno") } } catch (e) { s ^dk("err",$i(^dk("err")))=e.DisplayString() } Quit $$$OK } /// DisplayPermissions modified to include a text for BEST BANK account. ClassMethod DisplayPermissions(authorizationCode As %String, scopeArray As %ArrayOfDataTypes, currentScopeArray As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status { Set uilocales = properties.RequestProperties.GetAt("ui_locales") Set tLang = ##class(%OAuth2.Utils).SelectLanguage(uilocales,"%OAuth2Login") // $$$TextHTML(Text,Domain,Language) Set ACCEPTHEADTITLE = $$$TextHTML("OAuth2 Permissions Page","%OAuth2Login",tLang) Set USER = $$$TextHTML("User:","%OAuth2Login",tLang) Set POLICY = $$$TextHTML("Policy","%OAuth2Login",tLang) Set TERM = $$$TextHTML("Terms of service","%OAuth2Login",tLang) Set ACCEPTCAPTION = $$$TextHTML("Accept","%OAuth2Login",tLang) Set CANCELCAPTION = $$$TextHTML("Cancel","%OAuth2Login",tLang) &html<<html>> Do ..DrawAcceptHead(ACCEPTHEADTITLE) Set divClass = "permissionForm" Set logo = properties.ServerProperties.GetAt("logo_uri") Set clientName = properties.ServerProperties.GetAt("client_name") Set clienturi = properties.ServerProperties.GetAt("client_uri") Set policyuri = properties.ServerProperties.GetAt("policy_uri") Set tosuri = properties.ServerProperties.GetAt("tos_uri") Set user = properties.GetClaimValue("preferred_username") If user="" { Set user = properties.GetClaimValue("sub") } &html<<body>> &html<<div id="topLabel"></div>> &html<<div class="#(divClass)#">> If user '= "" { &html< <div> <span id="left" class="userBox">#(USER)#<br>#(##class(%CSP.Page).EscapeHTML(user))#</span> > } If logo '= "" { Set espClientName = ##class(%CSP.Page).EscapeHTML(clientName) &html<<span class="logoClass"><img src="#(logo)#" alt="#(espClientName)#" title="#(espClientName)#" align="middle"></span>> } If policyuri '= "" ! (tosuri '= "") { &html<<span id="right" class="linkBox">> If policyuri '= "" { &html<<a href="#(policyuri)#" target="_blank">#(POLICY)#</a><br>> } If tosuri '= "" { &html<<a href="#(tosuri)#" target="_blank">#(TERM)#</a>> } &html<</span>> } &html<</div>> &html<<form>> Write ##class(%CSP.Page).InsertHiddenField("","AuthorizationCode",authorizationCode),! &html<<div>> If $isobject(scopeArray), scopeArray.Count() > 0 { Set tTitle = $$$TextHTML(" is requesting these permissions:","%OAuth2Login",tLang) &html<<div class="permissionTitleRequest">> If clienturi '= "" { &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>> } Else { &html<#(##class(%CSP.Page).EscapeHTML(clientName))#> } &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>> Set tCount = 0 Set scope = "" For { Set display = scopeArray.GetNext(.scope) If scope = "" Quit Set tCount = tCount + 1 If display = "" Set display = scope Write "<div class='permissionItemRequest'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>" } } If $isobject(currentScopeArray), currentScopeArray.Count() > 0 { Set tTitle = $$$TextHTML(" already has these permissions:","%OAuth2Login",tLang) &html<<div>> &html<<div class="permissionTitleExisting">> If clienturi '= "" { &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>> } Else { &html<#(##class(%CSP.Page).EscapeHTML(clientName))#> } &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>> Set tCount = 0 Set scope = "" For { Set display = currentScopeArray.GetNext(.scope) If scope = "" Quit Set tCount = tCount + 1 If display = "" Set display = scope Write "<div class='permissionItemExisting'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>" } &html<</div>> } /*********************************/ /* BEST BANK CUSTOMIZATION */ /*********************************/ try { If properties.CustomProperties.GetAt("account_number")'="" { // Display the account number obtained from account context. Write "<div class='permissionItemRequest'><b>Selected account is "_properties.CustomProperties.GetAt("account_number")_"</b></div>",! // or, alternatively, let user add some more information at this stage (e.g. linked account number) //Write "<div>Account Number: <input type='text' id='accno' name='p_accno' placeholder='accno' autocomplete='off' ></div>",! } } catch (e) { s ^dk("err",$i(^dk("err")))=e.DisplayString() } /* original implementation code continues here... */ &html< <div><input type="submit" id="btnAccept" name="Accept" value="#(ACCEPTCAPTION)#"/></div> <div><input type="submit" id="btnCancel" name="Cancel" value="#(CANCELCAPTION)#"/></div> > &html<</form> </div>> Do ..DrawFooter() &html<</body>> &html<<html>> Quit 1 } /// For CUSTOM BESTBANK we need to validate that patient entered, /// ! javascript in this method is only needed when we let user enter some addtional data /// within DisplayPermissions method ! ClassMethod DrawAcceptHead(ACCEPTHEADTITLE) { &html<<head><title>#(ACCEPTHEADTITLE)#</title>> Do ..DrawStyle() &html< <script type="text/javascript"> function doAccept() { var accno = document.getElementById("accno").value; var errors = ""; if (accno !== null) { if (accno.length < 1) { errors = "Please enter account number name"; } } if (errors) { alert(errors); return false; } // submit the form return true; } </script> > &html<</head>> } } As you could see, the %OAuth2.Server.Properties class contains several arrays, that are passed around. These are: · RequestProperties – contains parameters from authorization request · CustomProperties – container for exchanging data between above mention · ResponseProperties – container for properties to be added to JSON response object to atoken request · ServerProperties – contains shared properties that authorization server exposes to customization code (e.g. logo_uri, client_uri etc…) Further it contains several “claims” properties, that are used to specify what claims have to be returned by the authorization server. In order to call this authentication page correctly, we modified our initial client page code so it looks like this: set scope="openid profile scope1 scope2 account" // this data comes from application (a form data or so...) and sets a context for our request // we can, through subclassing the Authenticate class, display this data to user so he/she can decide // whether to grant access or not set properties("accno")="75-452152122-5320" set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint( ..#OAUTH2APPNAME, scope, ..#OAUTH2CLIENTREDIRECTURI, .properties, .isAuthorized, .sc) if $$$ISERR(sc) { write "GetAuthorizationCodeEndpoint Error=" write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",! } As you can see, we added account scope and properties array node “accno” with a context value, which can originate at different part of our application. This value is passed inside access token to the resource server for further processing. There is a real life scenario that uses the above described logic – the FHIR standard for exchanging electronic patient records. Debugging The OAUTH framework comes with built-in debugging. This is very helpful as all communication between client and servers is encrypted. The debugging feature allows to capture traffic data generated by the API classes before they are sent over the wire. In order to debug your code, you can implement a simple routine or class according to the code below. Please bear in mind that you need to implement this code at all communicating InterSystems IRIS instances! In that case, you better provide a filename with name indicating its role within the process of OAUTH flow. (The sample code below is saved as rr.mac routine, but the name is up to you.) // d start^rr() start() public { new $namespace set $namespace="%sys" kill ^%ISCLOG set ^%ISCLOG=5 set ^%ISCLOG("Category","OAuth2")=5 set ^%ISCLOG("Category","OAuth2Server")=5 quit } // d stop^rr() stop() public { new $namespace set $namespace="%sys" set ^%ISCLOG=0 set ^%ISCLOG("Category","OAuth2")=0 set ^%ISCLOG("Category","OAuth2Server")=0 quit } // display^rr() display() public { new $namespace set $namespace="%sys" do ##class(%OAuth2.Utils).DisplayLog("c:\temp\oauth2_auth_server.log") quit } Then, before you start testing, open a terminal and invoke d start^rr() at all InterSystems IRIS nodes (client, auth server or resource server). Once you’re finished, run d stop^rr() and d display^rr() to populate log files. Summary In this series of articles, we have learned how to use InterSystems IRIS OAuth 2.0 implementation. Beginning with simple client application demonstration in part 1, followed by complex sample described in part 2. Finally, we described most important classes of the OAuth 2.0 implementation and explained when they should be called within user applications. My special thanks to Marvin Tener, for his endless patience when replying to my, sometimes dumb, questions and for reviewing the series.
Question
Ponnumani Gurusamy · Aug 11, 2016

I wrote my first class in InterSystems Caché. How can I run it?

Hi,I create a cache class program. Its compile it. But how to run it?Please send one sample program with output Thanks for your useful information. Hi Ponnumani. You cannot run class itself in Caché. You can run some classmethod of this class. For example from terminal you can run classmethod 'PrintPersons' of class Sample.Person as follows: USER>zn "samples" SAMPLES>do ##class(Sample.Person).PrintPersons() Name: Klein,Agnes E. Name: Willeke,Imelda R. Name: Vanzetti,Rhonda Y. ... I encourage you to read "Caché Programming Orientation Guide". http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GORIENT Particularly it contains information on how you can use terminal to test your programs: http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GORIENT_ch_devtools#GORIENT_devtools_terminal you can run like W ##Class(packagename.classname).classmethodname() (if it quits)
Announcement
Janine Perkins · Feb 9, 2016

Featured InterSystems Online Course- Introduction to Caché, Ensemble, HealthShare and Related Technologies

Do you want to learn about the different products and technologies InterSystems has to offer? Introduction to Caché, Ensemble, HealthShare and Related TechnologiesThis course gives a non-technical overview to InterSystems products and technologies. It will help you to explore the possibilities of your existing products, help you to identify products you may need to solve gaps at your workplace and allow you to have educated conversations about all InterSystems products, technologies and their capabilities from a non-technical perspective. Learn More.
Article
Tamara Lebedeva · Jan 28, 2017

Android push notifications with InterSystems Ensemble using Road Police fines as a sample application

Many mobile applications that enable users to get information about road fines and pay them, send notifications about newly added fines. This functionality can be efficiently implemented using push notifications sent to users’ devices.Our application was not an exception. The server side is based on the Ensemble platform that offers integrated support of push notifications starting from version 2015.1. Some theory firstPush notifications provide a way of distributing information where data come from the provider to the user according to a set of predefined parameters.In the most general case, the notification sending process for mobile devices looks the following way:Notification of mobile app users is carried out using special push notification delivery services. However, notifications cannot be sent arbitrarily. The user must be subscribed to a push notifications channel or to notifications from a particular app.Ensemble has the following instances for working with push notifications:» EnsLib.PushNotifications.GCM.Operation — a business operation for sending push notifications to the Google Cloud Messaging Services (GCM) server. This operation also supports the delivery of a single message to several devices at once.» EnsLib.PushNotifications.APNS.Operation — a business operation that sends a notification to the Apple Push Notifications server. Requires a separate SSL certificate for each destination application.» EnsLib.PushNotifications.IdentityManager — an Ensemble business process that allows you to send messages to the user without thinking about their quantity and the types of destination devices. In essence, Identity Manager contains a table that maps one user’s identifier to all of their devices. The Identity Manager’s business process receives messages from other components of the production and forwards them to the router that dispatches all GCM messages to a GCM operation, and each APNS message to an APNS operation configured with a corresponding SSL certificate.» EnsLib.PushNotifications.AppService – a business service that allows users to send push notifications generated outside of a production. In essence, the message itself can be generated somewhere inside Ensemble independently from a production, and the service makes it possible to send these messages from Ensemble. These classes are described in detail in the "Configuring and Using Ensemble Push Notifications" section of the Ensemble documentation.Now, a few words on how we implemented the notification processIn our case, messages are generated by a specially designed business process within a production, which is why we didn’t need a service. Also, at this stage, we only have an Android app, which is why we haven’t used APNS operations yet. In fact, we used the lowest-level method of sending messages via a GCM operation. In the future, when working on the iOS version of the app, it will be convenient to work with notifications via Identity Manager in order to avoid analyzing the types and number of devices. However, we will now talk more about GCM.In order to send notifications, you need to implement a process inside a production and include the necessary business operation. At the moment, we have two separate processes of push notification dispatch, each with its own logic: notifications about new fines, notifications about the expiry of fine discounts. We will describe each type individually.First, a few words about the general data scheme and the settings needed for all notifications to work.Let’s create an empty SSL configuration for the work of the operation and add it to the configuration of our business operation (for GCM only!).Let’s add an operation of the EnsLib.PushNotifications.GCM.Operation class to our production and configure its parameters:NotificationProtocol: HTTPPushServer: http://android.googleapis.com/gcm/sendAs the result, operation settings will look like this:We need to keep the client identifier, devices (types and identifiers), list of documents (driver’s licenses and vehicle registration certificates). All this information is received from the client in a notification subscription request. Therefore, we need the following classes:Client – for storing clients, App – for storing devices, Doc – for storing document data: Class penalties.Data.Doc Extends %Persistent { ///document type (VRC or DL) Property type As %String; ///document identifier Property value As %String; } Class penalties.Data.App Extends %Persistent { ///device type (GCM or APNS) Property Type As %String; ///device identifier Property ID As %String(MAXLEN = 2048); } Class penalties.Data.Client Extends %Persistent { /// the client’s email address from Google Play Services used as an identifier Property Email As %String; ///list of client’s devices Property AppList As list Of penalties.Data.App; ///list of documents the client is subscribed to Property Docs As list Of penalties.Data.Doc; } In order to be able to send notifications about new fines, we need to understand which fines the client has already seen, and which not. To do this, we use the class NotificationFlow, where we mark that the client has already received information about a particular finе. Class penalties.Data.NotificationFlow Extends %Persistent { ///client identifier (email in our case) Property Client As %String; ///fine identifier Property Penalty As %String; /// Status Property Sent As %Boolean; } For convenience, let’s omit the names of packages while referring to classes below. The content of these classes makes it clear how the process will look for new fines: for each client, we cycle through the list of documents, send queries to GIS GMP (main municipal system for state and municipal payments), check if the received fines are in NotificationFlow, and if they are, we remove them from the list. As the result, we end up with a list of fines that we need to notify the client about. Let’s cycle through the list of devices and send a push notification to each of them. Top level: where clientkey is a content property that by default equals to the ID of the first subscriber stored in the Client class. The sub-process looks like this: Let’s take a look inside of foreach blocks: After this foreach block, we have a ready request called EnsLib.PushNotifications.NotificationRequest, and we only need to add some text to messages. This is done in the foreach block for our docs. Finally, a small code fragment that fills the request data: ClassMethod getPenaltyforNotify(client As penalties.Data.Client, penaltyResponse As penalties.Operations.Response.getPenaltiesResponse, notificationRequest As EnsLib.PushNotifications.NotificationRequest) { set json="",count=0 set key="" for { set value=penaltyResponse.penalties.GetNext(.key) quit:key="" set find=0 set res=##class(%ResultSet).%New("%DynamicQuery:SQL") set exec="SELECT * FROM penalties_Data.NotificationFlow WHERE (Penalty = ?) AND (Client = ?)" set status=res.Prepare(exec) set status=res.Execute(value.billNumber,client.Email) if $$$ISERR(status) do res.%Close() kill res continue while res.Next() { if res.Data("Sent") set find=1 } do res.%Close() kill res if find { do penaltyResponse.penalties.RemoveAt(key), penaltyResponse.penalties.GetPrevious(.key) } else { set count=count+1 do notificationRequest.Data.SetAt("single","pushType") for prop="billNumber","billDate","validUntil","amount","addInfo","driverLicence","regCert" { set json=$property(value,prop) set json=$tr(json,"""","") if json="" continue do notificationRequest.Data.SetAt(json,prop) } set json="" set notObj=##class(penalties.Data.NotificationFlow).%New() set notObj.Client=client.Email set notObj.Penalty=value.billNumber set notObj.Sent=1 do notObj.%Save() } } if count>1 { set keyn="" for { do notificationRequest.Data.GetNext(.keyn) quit:keyn="" do notificationRequest.Data.RemoveAt(keyn) } do notificationRequest.Data.SetAt("multiple","pushType") do notificationRequest.Data.SetAt(count,"penaltiesCount") } } The process for fine discounts is implemented a bit differently. Here is what we have on the top level: Fines with a discount are selected using the following code: ClassMethod getSaleforNotify() { //lets clean the temporary global just in case kill ^mtempArray set res=##class(%ResultSet).%New("%DynamicQuery:SQL") //let’s look for still unpaid fines with a discount set exec="SELECT * FROM penalties_Data.Penalty WHERE status!=2 AND addInfo LIKE '%Скидка%'" set status=res.Prepare(exec) set status=res.Execute() if $$$ISERR(status) do res.%Close() kill res quit while res.Next() { set discDate=$piece(res.Data("addInfo"),"50% off if paid by: ",2) set discDate=$extract(discDate,1,10) set date=$zdh(discDate,3) set dayscount=date-$p($h,",") //will send it 5,2,1 and 0 days before if '$lf($lb(5,2,1,0),dayscount) continue set doc=$s(res.Data("regCert")'="":"sts||"_Res.Data("regCert"),1:"vu||"_Res.Data("driverLicence")) set clRes=##class(%ResultSet).%New("%DynamicQuery:SQL") //let’s look for clients subscribed to the document set clExec="SELECT * FROM penalties_Data.Client WHERE (Docs [ ?)" set clStatus=clRes.Prepare(clExec) set clStatus=clRes.Execute(doc) if $$$ISERR(clStatus) do clRes.%Close() kill clRes quit while clRes.Next() { //let’s make a convenient list to cycle through set ^mtempArray($job,clRes.Data("Email"),res.Data("billNumber"))=res.Data("billDate") } do clRes.Close() } do res.Close() } As the result, we have a global with fines broken down by clients. What we need to do now is to cycle through this global and send a fine to each client, but make sure it hasn’t been paid somewhere else: Let’s fall into the fines loop: Basically, the difference between the processes is the following: in the first scenario, we cycle through all our clients, but in the second scenario, we only select clients with fines of a particular type; in the first case, we send a single notification with a summary (there are clients who somehow manage to get fined several times a day), in the second case, we notify about each discount option individually. During debugging, we faced a problem with our messages that made us redefine some of our system methods. One of the parameters that is included in messages is the fine number that generally looks like this: “12345678901234567890”. The system classes that send notifications convert such strings into integers, but the GCM service that receives such large numbers gets really puzzled and, unfortunately, responds with a “Bad Request” error. That is why we redefined the system class of the operation. Now we call our own method ConvertArrayToJSON, where we call ..Quote with the second parameter equal to 0 – that is, we don’t convert strings containing numbers into integers, but leave them in the string format: Method ConvertArrayToJSON(ByRef pArray) As %String { #dim tOutput As %String = "" #dim tSubscript As %String = "" For { Set tSubscript = $ORDER(pArray(tSubscript)) Quit:tSubscript="" Set:tOutput'="" tOutput = tOutput _ "," Set tOutput = tOutput _ ..Quote(tSubscript) _ ": " If $GET(pArray(tSubscript))'="" { #dim tValue = pArray(tSubscript) If $LISTVALID(tValue) { #dim tIndex As %Integer // $LIST .. aka an array // NOTE: This only handles an array of scalar values Set tOutput = tOutput _ "[ " For tIndex = 1:1:$LISTLENGTH(tValue) { Set:tIndex>1 tOutput = tOutput _ ", " Set tOutput = tOutput _ ..Quote($LISTGET(tValue,tIndex),0) } Set tOutput = tOutput _ " ]" } Else { // Simple string Set tOutput = tOutput _ ..Quote(tValue,1) } } Else { // Child elements #dim tTemp Kill tTemp Merge tTemp = pArray(tSubscript) Set tOutput = tOutput _ ..ConvertArrayToJSON(.tTemp) } } Set tOutput = "{" _ tOutput _ "}" Quit tOutput } We did not face any other problems while implementing this. The basic things that need to be done for notifications to be sent are: Add the necessary operation Design a process that fills the following properties of the request: AppIdentifier — Server API Key received after registering the service with GCM, Identifiers — list of device identifiers we will be using, Service — the type of devices we will be addressing (GCM in our case), Data — request data (keep in mind that the array has a key-value structure). And that will be about it. The use of ready Ensemble components allowed us to spend just a couple of hours to implement this functionality, including debugging and testing. As the result, we have happy clients who are timely informed about new fines and early payment discounts. You can see this code in action in our Android and iOS apps.
Announcement
Olga Zavrazhnova · Mar 26, 2020

New Global Masters Reward: Hold a Webinar Supported by InterSystems about Your Solution!

Hi Community, New reward available on Global Masters Advocate Hub! Please welcome: ⚡️ Your Webinar supported by InterSystems ⚡️ Would you like to hold a professional webinar for developers and tell about your solution/tool and your company services? Redeem this prize for 3,000 points and we will help you to organize it! What you will get: InterSystems team will set up an online webinar; Promotion of the webinar on DC and social media; Landing page on Developers Community; Dry-run before and technical support during the webinar. Requirements: The application should work on InterSystems IRIS/IRIS for Health or be a tool to manage/develop with IRIS. If you have not joined InterSystems Global Masters Advocacy Hub yet, let's get started right now! Details you will find here. Additional information about Global Masters: How to join InterSystems Global Masters Global Masters Levels Descriptions Global Masters Badges Description - full rules on how to achieve Feel free to ask your questions regarding Global Masters here! Hey Community! Yesterday, we held the first webinar supported by InterSystems for our GM Advocate @Dmitry.Maslennikov. It was a success - more than 80 participants! 👏🏼 So, who's next? And how else can your business grow with InterSystems? ➡️ Please don't miss this post!
Announcement
Sergey Lukyanchikov · Aug 18, 2020

InterSystems IRIS ML Toolkit orchestrating Microsoft Azure Data Factory and Machine Learning

ML Toolkit for InterSystems IRIS, besides Python/R/Julia, allows orchestrating cloud-based advanced analytics services, such as Microsoft Azure Data Factory and Machine Learning:
Article
Alberto Fuentes · Feb 24, 2021

Workshop exercises: develop REST APIs and manage them using InterSystems API Manager

Hi Community!  Today I'd like to share with you a link to some exercises from a workshop about developing REST APIs and how to manage them using *InterSystems API Manager*. It includes step by step exercises to: * Developing REST APIs from *OpenAPI* specifications. * Adding the created API in an interoperability production (optional). * Basic API management in InterSystems API Manager (service, route, auth, consumers, rate limiting, etc.). * Some other more complex scenarios in InterSystems API Manager like **load balancing**, or **routing by header**. You will find it all here: https://openexchange.intersystems.com/package/workshop-rest-iam
Announcement
Benjamin De Boe · Apr 21, 2021

InterSystems IRIS, IRIS for Health and Health Connect 2021.1 previews are now published

Preview releases are now available for the 2021.1 version of InterSystems IRIS, IRIS for Health and HealthShare Health Connect. As this is a preview release, we are eager to learn from your experiences with this new release ahead of its General Availability release next month. Please share your feedback through the Developer Community so we can build a better product together. InterSystems IRIS Data Platform 2021.1 is an extended maintenance (EM) release. Many important new capabilities and enhancements have been added in continuous delivery (CD) releases since 2020.1, the previous EM release. Please refer to the release notes for 2020.2, 2020.3 and 2020.4 for an overview of these. The enhancements in this release offer developers more freedom to build fast and robust application in their language of choice and enable users to consume large amounts of information more effectively through new and faster analytics capabilities. With InterSystems IRIS 2021.1, customers can deploy InterSystems IRIS Adaptive Analytics, an add-on product that extends InterSystems IRIS to deliver greater ease of use, flexibility, scalability, and efficiency to analytics end users regardless of their business intelligence (BI) tools of choice. It enables defining an analytics-friendly business model and transparently accelerates analytic query workloads that run against this model by autonomously building and maintaining interim data structures in the background. Other spotlight features new in this release include: a consolidated set of External Language Gateways, with improved manageability and now including R and Python to build robust and scalable server-side code in your language of choice the InterSystems Kubernetes Operator (IKO) offers declarative configuration and automation for your environments, and now also supports deploying InterSystems System Alerting & Monitoring (SAM) InterSystems API Manager v1.5, including an improved user experience and Kafka support mainstream availability of IntegratedML, enabling SQL developers to build and deploy Machine Learning models directly in a purely SQL environment InterSystems IRIS for Health 2021.1 includes all of the enhancements of InterSystems IRIS. In addition, this release further extends the platform's extensive support for the FHIR® standard through APIs for parsing & evaluating FHIRPath expressions against FHIR data. This is in addition to the significant FHIR-related capabilities released since 2020.1, including support for FHIR Profiles, FHIR R4 Transforms and the FHIR client API. More details on all of these features can be found in the product documentation: InterSystems IRIS 2021.1 documentation and release notes InterSystems IRIS for Health 2021.1 documentation and release notes HealthShare Health Connect 2021.1 documentation and release notes EM releases come with classic installation packages for all supported platforms, as well as container images in OCI (Open Container Initiative) a.k.a. Docker container format. For a complete list, please refer to the Supported Platforms document. Installation packages and preview keys are available from the WRC's preview download site. Container images for the Enterprise Editions of InterSystems IRIS and IRIS for Health and all corresponding components are available from the InterSystems Container Registry using the following commands: docker pull containers.intersystems.com/intersystems/iris:2021.1.0.205.0 docker pull containers.intersystems.com/intersystems/irishealth:2021.1.0.205.0 For a full list of the available images, please refer to the ICR documentation. Container images for the Community Edition can also be pulled from the Docker store using the following commands: docker pull store/intersystems/iris-community:2021.1.0.205.0 docker pull store/intersystems/iris-community-arm64:2021.1.0.205.0 docker pull store/intersystems/irishealth-community:2021.1.0.205.0 docker pull store/intersystems/irishealth-community-arm64:2021.1.0.205.0 Alternatively, tarball versions of all container images are available via the WRC's preview download site. InterSystems IRIS Studio 2021.1 is a standalone IDE for use with Microsoft Windows and can be downloaded via the WRC's preview download site. It works with InterSystems IRIS and IRIS for Health version 2021.1 and below. InterSystems also supports the VSCode-ObjectScript plugin for developing applications for InterSystems IRIS with Visual Studio Code, which is available for Microsoft Windows, Linux and MacOS. Other standalone InterSystems IRIS 2021.1 components, such as the ODBC driver and web gateway, are available from the same page. The build number for this preview release is 2021.1.0.205.0. Thanks, @Benjamin.DeBoe ! Is there a special build for IntegratedML as we had before? not anymore. As of 2021.1, it's part of the main kit (aka "mainstream availability of IntegratedML" in the announcement above ) Got you! Great! Hope this didn't increase kits dramatically ) And we updated the images with ZPM 0.2.14 too: intersystemsdc/iris-community:2021.1.0.205.0-zpm intersystemsdc/iris-community:2020.4.0.547.0-zpm intersystemsdc/irishealth-community:2021.1.0.205.0-zpm intersystemsdc/irishealth-community:2020.4.0.547.0-zpm And to launch IRIS do: docker run --rm --name my-iris -d --publish 9091:1972 --publish 9092:52773 intersystemsdc/iris-community:2021.1.0.205.0-zpm docker run --rm --name my-iris -d --publish 9091:1972 --publish 9092:52773 intersystemsdc/iris-community:2020.4.0.547.0-zpm docker run --rm --name my-iris -d --publish 9091:1972 --publish 9092:52773 intersystemsdc/irishealth-community:2021.1.0.205.0-zpm docker run --rm --name my-iris -d --publish 9091:1972 --publish 9092:52773 intersystemsdc/irishealth-community:2020.4.0.547.0-zpm And for terminal do: docker exec -it my-iris iris session IRIS and to start the control panel: http://localhost:9092/csp/sys/UtilHome.csp To stop and destroy container do: docker stop my-iris On the Login page, it does not show IRIS Logo. IRIS just returns 404 for the icon, if it's present, after the first login, it appears in WebGateway cache and became available. And the same for any static files. The Security Audit shows this error. <PROTECT>%Oid+3^%Stream.Object.1 ^IRIS.SM.Shard,/usr/irissys/mgr/ Thanks for bringing it up. This had already been fixed in our working branch, but the fact that the port to 2021.1 was missing unfortunately escaped our automated tests. The fix will obviously be in the GA release, but we might publish another preview build before then to pick up this fix.
Question
RIddhi Jadeja · May 10, 2021

I want to install Management Portal Engine for Windows to create Cache InterSystems DB

I want to create a Sample Database for Cache InterSystems and want to access it via Cache Entity Framework Provider. But, I cannot find a particular engine of Management Portal to create a database. Can you guide how to install Management Portal in Windows? Management Portal is a part of the default installation. And in windows, should be available from the menu by the InterSystems Cube icon in the tray. And usually, the URL is something like this. The port can be different if you have more than one instance of Cache installed. http://localhost:57772/csp/sys/UtilHome.csp Hello Dmitry, Thank you for your response. Actually I have not Cache instance installed in my system. So, I think I have to install Cache first. Can you guide me how to install that, Actually I'm new to this? If you are new to InterSystems, you should start with IRIS, which is the newest product, the replacement for Caché. You can download the distributive here or through WRC if you already have access. The installation process on Windows is quite simple, just run the installer, and press the buttons Next. It will be enough for the evaluation. You may look at the documentation, for the Installation guide. Actually, our client is using Cache so I want to process with that only. Can you guide for Cache InterSystems installation please? The guide is here . But in this case, you will need to get Caché distributive from your client. Thank you. I have read this guide but couldn't find any installation kit or exe to install this into the system. As I said, you have to get it from your client, It's no publicly available anymore. Okay. Thank you so much.
Announcement
Anastasia Dyubaylo · May 17, 2021

Demo of REST APIs defined in Swagger, built with InterSystems IRIS interfacing with a SQL backend

Hey Developers, Enjoy watching this new video on InterSystems Developers YouTube: ⏯ Demo of REST APIs defined in Swagger, built with InterSystems IRIS interfacing with a SQL backend Simple demo of IRIS exposing REST APIs created based on Swagger that have a SQL database as the backend. ⬇️ irisdemo-demo-restsql on Open Exchange 🗣 Speaker: @Amir.Samary, Director - Solution Architecture, InterSystems Stay tuned! 👍🏼
Announcement
Derek Robinson · Jun 3, 2021

Data Points Ep. 19: What's New in Version 2021.1 of InterSystems IRIS?

Episode 19 of Data Points features a conversation with Benjamin De Boe about the 2021.1 release of InterSystems IRIS! The conversation covers new improvements in analytics, business intelligence, machine learning, development gateways, FHIR capability, and more. For all episodes of Data Points and to subscribe on your favorite app, check out https://datapoints.intersystems.com.
Announcement
Anastasia Dyubaylo · Mar 25, 2021

Webinar: InterSystems IRIS & the industry 4.0 roadmap - Smart Factory Starter Pack

Hey Developers, We're pleased to invite you to the upcoming webinar in English called "InterSystems IRIS & the industry 4.0 roadmap - Smart Factory Starter Pack"! 🗓 Date & time: March 31, 02:00 PM CEST 🗣 Speakers: @Marco.denHartog, CTO, ITvisors @Aldo.Verlinde9118, Sales Engineer, InterSystems Manufacturing organizations today are rapidly transforming their existing factories into “smart factories.” In a smart factory, data from Operational Technologies (OT) systems and real-time signals from the shop floor are combined with enterprise IT and analytics applications. This enables manufacturers to improve quality and efficiency, respond faster to events, and predict and avoid problems before they occur, among many other benefits. In this webinar, we discuss the 5 maturity levels towards Industry 4.0 from a data-driven point of view and how the IRIS and the Smart Factory Starter Pack are helping manufacturers at each level: Data Collection: Easy connections to many different types of sources Data Unification: Clean data, data management, bridging the OT/IT gap, … Data Exploration: Variety of tools, connectivity (!), translytical database, … Operationalization: Minimize operator overhead & errors, two-way connections (ERP, MES, order flow), flexible & easy to use database, … Industry 4.0: Modelling production processes & equipment, predicting quality & performance, continuous improvement cycle Discover how IRIS and the Smart Factory Starter Pack empower manufacturers in their smart factory initiatives. Note: The language of the webinar is English. ➡️ JOIN THE WEBINAR!
Announcement
Anastasia Dyubaylo · Apr 15, 2021

New Video: Deploying InterSystems IRIS Docker Solutions to GKE Cloud in 5 Minutes

Hi Developers, Please welcome the new video specially recorded for Developer Tools programming contest: ⏯ Deploying InterSystems IRIS docker solutions to GKE cloud in 5 minutes This video will demo how one can use a template of the InterSystems IRIS data platform, build a solution using Docker and deploy it with an arbitrary DNS with HTTPS enabled as a working service on Google cloud using GKE and cloud run technology. ⬇️ iris-google-run-deploy-template This demo supports the programming contest happening now and lets all the participants use the secret key and deploy their contest solutions on the InterSystems account at name.contest.community.intersystems.com. The key could be obtained in a Docker channel in Discord. 🗣 Presenter: @Evgeny.Shvarov, Developer Ecosystem Manager, InterSystems Stay tuned! 👍🏼
Announcement
Evgeny Shvarov · Nov 30, 2022

Technical Bonuses Results for InterSystems IRIS for Health Contest: FHIR for Women's Health 2022

Hi Developers! Here is the score of technical bonuses for participants' applications in the InterSystems IRIS for Health Contest: FHIR for Women's Health! Project Women’s Health Topic Women’s Health Dataset IRIS For Health or FHIR Cloud Usage Healthcare Interoperability Embedded Python Docker ZPM Online Demo Code Quality First Article on DC Second Article on DC Video on YouTube First Time Contribution Total Bonus Nominal 5 3 2 4 3 2 2 2 1 2 1 3 3 33 FemTech Reminder 5 2 4 2 2 2 1 2 3 3 28 FHIR Questionnaires 2 2 2 1 2 3 12 ehh2022-diabro 2 3 3 8 Dia-Bro-App 2 2 2 2 1 3 3 15 Dexcom Board 2 3 3 8 Beat Savior 2 3 3 8 Contest-FHIR 2 3 1 2 1 9 NeuraHeart 2 2 2 3 3 12 Pregnancy Symptoms Tracker 5 2 2 2 2 1 2 1 3 20 fhir-healthy-pregnancy 5 2 2 1 3 3 16 iris-fhir-app 2 4 2 2 2 1 2 15 Bonuses are subject to change upon the update. Please claim here in the comments below or in the Discord chat. Hello, Evgeny, thank you for this information. If I'm not mistaken we have opportunity (FemTech Reminder) to get following bonuses: ZPM, IRIS For Health or FHIR Cloud Usage Could you please check ehh2022-diabro had working online demo Our deployment on https://portal.events.isccloud.io/ has been deleted for some reason without our knowledge :'( NeuraHeart uses docker for our deployment and we use a python microservice for running machine learning tasks. HI! You achieved Docker bonus! But Embedded Python is new InterSystems technology, you don't use it. Read more about Embedded Python: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_epython Hi @Maksym.Shcherban ! Bring my apologies for that! The "life" of all the deployments on https://portal.events.isccloud.io/ is 7 days long by default. Could you create a new one? Also if you need the deployment to keep alive longer please reach me out in direct message. Points adjusted, thank you! Hey team, I would like to request the 3 points for the Youtube video Application Pregnancy Symptoms Tracker Also, another check ✅ for Quality Code as you can find here https://community.objectscriptquality.com/dashboard?id=intersystems_iris_community%2Fpregnancy-symp-tracker-fhir-app Updated
Discussion
Olga Zavrazhnova · Oct 31, 2022

Watch on-demand: What is the best source control system for development with InterSystems IRIS

Hi Community,Watch the recording of the Second Community Roundtable: "What is the best source control system for development with InterSystems IRIS?" Some great discussions have been started during this roundtable. We invite you to continue these discussions. in the comments to this post. Tell us, which source control you use and why – in the comments to this post.