Search

Clear filter
Article
Lucas Enard · Nov 27, 2022

Easy CSV TO FHIR - InterSystems Contest

Hello everyone, I’m a French student in academical exchange for my fifth year of engineering school and here is my participation in the FHIR for Women's Health contest. This project is supposed to be seen as the backend of a bigger application. It can be plugged into a Front End app and help you gather information from your patients. It will read your data in local and use a Data Transformation to make it into a FHIR object before sending it to the included local FHIR server. I wanted to participate because Women's Health is a really important topic that must be discussed more. Using my work you can easily transform any CSV file into a FHIR resource to gather information from your patient on any important subject. It could be used to gather information on pregnant women, then these data ( stored in FHIR in IRIS ) could be analyzed to see if there is any problem or complication. They can also be reviewed by a doctor for example. See [my GitHub](https://github.com/LucasEnard/Contest-FHIR) and the Open Exchange post linked to this article. Or see the ReadMe here : # 1. Contest-FHIR This is a full python IRIS production that gather information from a csv, use a DataTransformation to make it into a FHIR object and then, save that information to a FHIR server. This use interop as well as FHIR. The objective is to help people understand how easy it is to use FHIR. I didn't have the time to create a csv about women's health, but you can easily imagine this application using a csv on Women's Health. - [1. Contest-FHIR](#1-contest-fhir) - [2. Prerequisites](#2-prerequisites) - [3. Installation](#3-installation) - [3.1. Installation for development](#31-installation-for-development) - [3.2. Management Portal and VSCode](#32-management-portal-and-vscode) - [3.3. Having the folder open inside the container](#33-having-the-folder-open-inside-the-container) - [4. FHIR server](#4-fhir-server) - [5. Walkthrough](#5-walkthrough) - [5.1. Messages and objects](#51-messages-and-objects) - [5.2. Business Service](#52-business-service) - [5.3. Business Process](#53-business-process) - [5.4. Business Operation](#54-business-operation) - [5.5. Conclusion of the walkthrough](#55-conclusion-of-the-walkthrough) - [6. Creation of a new DataTransformation](#6-creation-of-a-new-datatransformation) - [7. What's inside the repo](#7-whats-inside-the-repo) - [7.1. Dockerfile](#71-dockerfile) - [7.2. .vscode/settings.json](#72-vscodesettingsjson) - [7.3. .vscode/launch.json](#73-vscodelaunchjson) # 2. Prerequisites Make sure you have [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Docker desktop](https://www.docker.com/products/docker-desktop) installed. If you work inside the container, as seen in [3.3.](#33-having-the-folder-open-inside-the-container), you don't need to install fhirpy and fhir.resources. If you are not inside the container, you can use `pip` to install `fhirpy` and `fhir.resources`. Check [fhirpy](https://pypi.org/project/fhirpy/#resource-and-helper-methods) and [fhir.resources](https://pypi.org/project/fhir.resources/) for morte information. # 3. Installation ## 3.1. Installation for development Clone/git pull the repo into any local directory e.g. like it is shown below: ``` git clone https://github.com/LucasEnard/fhir-client-python.git ``` Open the terminal in this directory and run: ``` docker-compose up --build ``` This will create the IRIS Framework and the FHIR Server. Once you are done building enter the Management portal, open the interop tab, open the Production and start it. Now you will see that the CSV was automatically read from your local files and added to the FHIR server. This app will periodically read any csv inside the "csv" folder and will send it to the FHIR server, doing the Data Transformation needed. ## 3.2. Management Portal and VSCode This repository is ready for [VS Code](https://code.visualstudio.com/). Open the locally-cloned `fhir-client-python` folder in VS Code. If prompted (bottom right corner), install the recommended extensions. ## 3.3. Having the folder open inside the container You can be *inside* the container before coding if you wish. For this, docker must be on before opening VSCode. Then, inside VSCode, when prompted (in the right bottom corner), reopen the folder inside the container so you will be able to use the python components within it. The first time you do this it may take several minutes while the container is readied. If you don't have this option, you can click in the bottom left corner and `press reopen in container` then select `From Dockerfile` [More information here](https://code.visualstudio.com/docs/remote/containers) ![Architecture](https://code.visualstudio.com/assets/docs/remote/containers/architecture-containers.png) By opening the folder remote you enable VS Code and any terminals you open within it to use the python components within the container. # 4. FHIR server To complete this walktrough we will use a fhir server. This fhir server was already build-in when you cloned and build the container. The url is `localhost:52773/fhir/r4` # 5. Walkthrough Complete walkthrough of the Python IRIS production. ## 5.1. Messages and objects Objects and messages will hold the information between our services,processes and opeartions. In the `obj.py` file we have to create a dataclass that match the csv, this will be used to hold the information before doing the DataTransformation. In our example the organization.csv csv looks like this, ``` active;name;city;country;system;value true;Name1;city1;country1;phone;050678504 false;Name2;city2;country2;phone;123456789 ``` Therefore, the object will look like this, ```python @dataclass # > This class represents a simple organization class BaseOrganization: active:bool = None name:str = None city:str = None country:str = None system:str = None value:str = None ``` In the `msg.py` file, we will have two type of request, the first one hold information of an organization before the DataTransformation and the second one can hold the information of the organization after the DataTransformation. ## 5.2. Business Service In the `bs.py` file we have the code that allows us to read the csv and for each row of the csv ( so for each organization ), map it into an object we created earlier. Then, for each of those row ( organization ) we create a request and send it to our process to do the DataTransformation. ```python # We open the file with open(self.path + self.filename,encoding="utf-8") as csv: # We read it and map it using the object BaseOrganization from earlier reader = DataclassReader(csv, self.fhir_type ,delimiter=";") # For each of those organization, we can create a request and send it to the process for row in reader: msg = OrgaRequest() msg.organization = row self.send_request_sync('Python.ProcessCSV',msg) ``` ## 5.3. Business Process In the `bp.py` file we have the DataTransformation, converting a simple python object holding little information to a FHIR R4 object. Here are the steps to do a DataTransformation using embedded python on our simple organization, ```python # Creation of the object Organization organization = Organization() # Mapping of the information from the request to the Organization object organization.name = base_orga.name organization.active = base_orga.active ## Creation of the Address object and mapping of the information ## from the request to the Address object adress = Address() adress.country = base_orga.country adress.city = base_orga.city ### Setting the adress of our organization to the one we created organization.address = [adress] ## Creation of the ContactPoint object and mapping of the ## information from the request to the ContactPoint object telecom = ContactPoint() telecom.value = base_orga.value telecom.system = base_orga.system ### Setting the telecom of our organization to the one we created organization.telecom = [telecom] # Now, our DT is done, we have an object organization that is a # FHIR R4 object and holds all of our csv information. ``` After that, our mapping is done and our DT is working. Now, we can send this newly created FHIR R4 resource to our FhirClient that is our operation. ## 5.4. Business Operation In the `bo.py` file we have the FhirClient, this client creates a connection to a fhir server that will hold the information gathered through the csv. In this example, we use a local fhir server who doesn't need an api key to connect. To connect to it we have to use in the on_init function, ```python if not hasattr(self,'url'): self.url = 'localhost:52773/fhir/r4' self.client = SyncFHIRClient(url=self.url) ``` Now, when we receive a message/request, we can, by finding the resource type of the resource we send with our request to the client, create an object readable by the client, and then save it to the fhir server. ```python # Get the resource type from the request ( here "Organization" ) resource_type = request.resource["resource_type"] # Create a resource of this type using the request's data resource = construct_fhir_element(resource_type, request.resource) # Save the resource to the FHIR server using the client self.client.resource(resource_type,**json.loads(resource.json())).save() ``` It is to be noted that the fhir client works with any resource from FHIR R4 and to use and change our example, we only need to change the DataTransformation and the object the holds the csv information. ## 5.5. Conclusion of the walkthrough If you have followed this walkthrough you now know exactly how to read a csv of a represetation of a FHIR R4 resource, use a DataTransformation to make it into a real FHIR R4 object and save it to a server. # 6. Creation of a new DataTransformation This repository is ready to code in VSCode with InterSystems plugins. Open `/src/python` to start coding or using the autocompletion. **Steps to create a new transformation** To add a new transformation and use it, the only things you need to do is add your csv named `Patient.csv` ( for example ) in the `src/python/csv` folder. Then, create an object in `src/python/obj.py` called `BasePatient` that map your csv. Now create a request in `src/python/msg.py` called `PatientRequest` that has a variable `resource` typed BasePatient. The final step is the DataTransformation, for this, go to `src/python/bp.py` and add your DT. First add `if isinstance(request, PatientRequest):` and then map your request resource to a fhir.resource Patient. Now if you go into the management portal and change the setting of the `ServiceCSV` to add `filename=Patient.csv` you can just start the production and see your transformation unfold and you client send the information to the server. **Detailled steps to create a new transformation** If you are unsure of what to do or how to do it here is a step by step creation of a new transformation : Create the file `Patient.csv` n the `src/python/csv` folder and fill it with: ``` family;given;system;value FamilyName1;GivenName1;phone;555789675 FamilyName2;GivenName2;phone;023020202 ``` Our CSV hold a family name, a given name and a phone number for two patients. In `src/python/obj.py` write : ```python @dataclass class BasePatient: family:str = None given:str = None system:str = None value:str = None ``` In `src/python/msg.py` write: ```python from obj import BasePatient @dataclass class PatientRequest(Message): resource:BasePatient = None ``` In `src/python/bp.py` write: ```python from msg import PatientRequest from fhir.resources.patient import Patient from fhir.resources.humanname import HumanName ``` In `src/python/bp.py` in the `on_request` function write: ```python if isinstance(request,PatientRequest): base_patient = request.resource patient = Patient() name = HumanName() name.family = base_patient.family name.given = [base_patient.given] patient.name = [name] telecom = ContactPoint() telecom.value = base_patient.value telecom.system = base_patient.system patient.telecom = [telecom] msg = FhirRequest() msg.resource = patient self.send_request_sync("Python.FhirClient", msg) ``` Now if you go into the management portal and change the setting of the `ServiceCSV` to add `filename=Patient.csv` you can just stop and restart the production and see your transformation unfold and you client send the information to the server. ![Settings](https://user-images.githubusercontent.com/77791586/170278879-02eb4303-51af-45ba-93bf-393e9ff5ed94.png) # 7. What's inside the repo ## 7.1. Dockerfile The simplest dockerfile to start a Python container. Use `docker-compose up` to build and reopen your file in the container to work inside of it. ## 7.2. .vscode/settings.json Settings file. ## 7.3. .vscode/launch.json Config file if you want to debug. Great example how to convert an CSV file to FHIR. Thanks for the contribution.
Announcement
Anastasia Dyubaylo · Dec 23, 2022

[Video] Introduction to Analytics with InterSystems IRIS

Hey Developers, Enjoy watching the new video on InterSystems Developers YouTube: ⏯ Introduction to Analytics with InterSystems IRIS Get an introduction to the analytics capabilities of InterSystems IRIS data platform and InterSystems IRIS for Health. Learn about the embedded analytics tools and third-party tools supported by InterSystems IRIS and see how you can use these tools for your own analytics needs. Dive in and stay tuned! 👍
Announcement
Anastasia Dyubaylo · Dec 30, 2022

[Video] InterSystems Cloud Services Overview

Hi Developers, Enjoy watching the new video on InterSystems Developers YouTube: ⏯ InterSystems Cloud Services Overview See how InterSystems Cloud Services provide targeted, low-barrier solutions to a wide range of issues. Learn how you can simplify your infrastructure, accelerate application development, and reduce the overhead in daily operations and maintenance using these services. Enjoy watching and stay tuned! 👍 whata great overview of our new Cloud offerings ... well done :)
Announcement
Anastasia Dyubaylo · Jan 6, 2023

[Video] What is InterSystems FHIR Server

Hi Community, Watch this video to learn about InterSystems FHIR Server, the turnkey FHIR data solution that empowers FHIR application developers to focus on building life-changing healthcare applications: ⏯️ What is InterSystems FHIR Server Subscribe to InterSystems Developers YouTube to stay tuned for more!
Question
Naveenkumar M · Nov 1, 2022

APIs to get the data in InterSystems Cache

Do we have REST API documentation to retrieve the data available in InterSystems Cache. I am looking for APIs that will help me to pull the data available in InterSystems Cache. Hi Naveenkumar, I think the first look does a great job of walking through this. Hope this is helpful: https://docs.intersystems.com/iris20222/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_rest The "Full Stack Tutorial" link on https://developer.intersystems.com/ is another source of help for building REST APIs. Also there are a plenty of examples in OpenExchange, which you can try via ZPM (IRIS package manager): https://openexchange.intersystems.com/?search=REST&sort=d.desc
Discussion
Vadim Aniskin · Feb 24, 2023

Dark version of InterSystems Developer Community

Hi everybody! Many developers prefer a dark version of applications. Not surprisingly, @Guillaume.Rongier7183 posted an idea of making a dark version for the Developer Community. So we look forward to hearing from you. Please send us your feedback by using this Poll on the Ideas Portal, and by voting and commenting on the relevant idea. Thank you in advance for your votes and have a good day! if it's not the default I won't care Third choice could be phrased better: I suggest "Doesn't matter, I will use Community regardless of interface color." Thank you so much, John!I really appreciate your advice. Please ensure that any dark version uses our white logo as the default color is invisible in dark mode. @Guillaume.Rongier7183 Thank you for your idea. We got several comments on this idea here and on the Ideas Portal. We discussed this idea with developers to estimate the resources needed. We got 3 votes supporting this idea, and based on poll results 56% of users answered that they'll not use the Dark version. Based on this input we move this idea to the status "Future consideration". We'll discuss the Dark version again during Community portal development planning. Thank for the feedback. I think I will continue to use my sunglasses to check the community at night for a while
Article
Anastasia Dyubaylo · May 26, 2023

What is InterSystems SSO and how it's used

Hi Community, You probably have already seen this abbreviation SSO on different InterSystems sites: Community, Learning Portal, Global Masters, etc. It basically stands for Single Sign-On. This means that you need only one set of login and password and you can access all the Developer Ecosystem resources. Thus, you create your account once and then use it everywhere in the InterSystems Ecosystem. Just look at the top menu with all the interesting resources that will help you grow professionally and personally: Use it well!
Article
Andreas Schneider · Feb 22, 2017

Using InterSystems Caché and Apache Zeppelin

I' have done some tests with Caché and Apache Zeppelin. I want to share my experince to use both systems together. I'll try to describe all steps that are required to config Zeppelin to connect to Caché. What is Apache Zeppelin? For all who think: What the heck is Apache Zeppelin?, here some details what the project site (http://zeppelin.apache.org) says: "A web-based notebook that enables interactive data analytics. You can make beautiful data-driven, interactive and collaborative documents with SQL, Scala and more. Apache Zeppelin interpreter concept allows any language/data-processing-backend to be plugged into Zeppelin. Currently Apache Zeppelin supports many interpreters such as Apache Spark, Python, JDBC, Markdown and Shell" Install Apache Zeppelin The next 5 steps describe how to get Apache Zeppelin up and running: You need a Java Runtime Environment. I you haven't download and install from here Download Zeppelin from here Extract the entire Zeppelin Zip-File into a folder like d:\zeppelin Open a shell (cmd on windows) and navigate into the folder \zeppelin\bin Execute zeppelin.bat on Windows to start Zeppelin Open up a browser and use this url http://localhost:8080 to open the main page of zeppelin. You should see something like this: Well done! Zeppelin is now up and running! Connect to Caché Now let us introduce Caché. The next steps describe how to create a jdbc connection to a Caché Namespace. Navigate to the menu item "anonymouse" -> "Interpreter" and scroll down to the "jdbc" section. Now press edit and go to the end of the jdbc section and enter your Caché JDBC connection string cache.driver = com.intersys.jdbc.CacheDriver cache.password = ??? cache.url = jdbc:Cache://<server\ip>:<port>/<namespace> cache.user = _SYSTEM Add the path to the Caché JDBC driver in the "Dependencies". This is located at the end of jdbc section. Press SAVE Great! All preparations are done, now let us use Zepplin and Caché. Query Caché Follow these steps to create a first Notebook to query and visualize some Caché data. Press create new note and name it like "HELLO WORLD" ... By typing %jdbc(cache) you inform zeppelin what data source you want to query.In detail: The %jdbc keyword lets zeppelin call the jdbc interpreter and the cache routes the query to the Caché connection. If you want to use more connections to Caché e.g. another namespace you have to create more entries in the jdbc section you've done before. The prefix of the entries are the connection name. Ok now place a sql statement in the next line. After that press execute and you will immediately see the result: Happy testing! Nice article Andreas!Have you perhaps also looked into creating a more advanced interpreter, rather than just leveraging the JDBC one? I know that's probably a significantly more elaborate thing to do, but a notebook-style interface for well-documented scripting would nicely complement the application development focus of Atelier / Studio.Thanks,benjamin Thanks Benjamin!Yes I've checked the sourcecode to see how a interpreter is done. It looks not like rocket science.. probably I've not completely understand the details, so I can say that ;-)But! ... I have no idea which functionality would be helpful in an more advanced Caché interpreter. Can you give me a hint on what features do you think, please?It should be very easy to implement something like a simple code completion with Caché Keywords to use in a notebook. But at this time I think all is limited to the JDBC interface, and with that to SQL.Would be really great if we could use also COS to fetch data... !? yes, the two-word feature called "executing COS" would probably be quite a step up. It was more a loose idea than something I've researched thoroughly, but maybe the authors of the Caché Web Terminal have some clues on how the connectivity should work (JDBC won't pull it). I'll created a fork on github (https://github.com/andreas5588/zeppelin) for Apache Zeppelin. My plan is to create a Caché Interpreter (first JDBC based) to learn and unterstand the architecture of Zeppelin.After this it would be great to extend this Interpreter to allow to query the Caché Data via COS. For this I'll contact the "Caché Web Terminal" guys maybe they can help...All are very welcome support this open source Interpreter. So feel free to jump in and support this project by coding, idea, testing ... or what ever ;-) Here's an article on how to write your own zeppelin interpriner. The article is considered as InterSystems Data Platform Best Practice.
Article
Eduard Lebedyuk · Apr 3, 2017

Firefox search engine for InterSystems documentation

If you're using Firefox, you probably know that there are search engine addons that allow us to use website search directly. Now, docs.intersystems.com is also available as a search engine here. Here's how it looks:And after clicking on InterSystems search engine you're redirected to the docs.intersystems.com:Download here.UPD. Added InterSystems IRIS documentation. Download. Very neat! Would be much better, if InterSystems will add OpenSearch to their documentation. And I think it will be enough only for the public version. And in this case, it will be supported in all modern browsers, without installation anything external. Something like this. <?xml version="1.0"?> <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> <ShortName>InterSystems docs latest</ShortName> <Description>InterSystems Ensemble Documentation latest Version</Description> <Url type="text/html" method="get" template="https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.SearchPageZen.cls?KEY=&KeyWord={searchTerms}"/> </OpenSearchDescription> Well, that's actually exactly what this plugin is. <?xml version="1.0" encoding="UTF-8"?> <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"> <ShortName>InterSystems</ShortName> <Description>Latest InterSystems documentation search</Description> <InputEncoding>UTF-8</InputEncoding> <Image width="16" height="16">data:image/png;base64,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 </Image> <Url type="text/html" method="GET" template="http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.SearchPageZen.cls" rel="searchform"> <Param name="KeyWord" value="{searchTerms}"/> </Url> </OpenSearchDescription> Added InterSystems IRIS documentation search engine. Download. very comfortable as I always use FFX for searching Hi, Ed!Is it possible to introduce a similar feature for Chrome users too? Sure.Open ChromeGo to Settings | Manage search engines...Scroll to the bottom of the windowIn Add a new search engine, enter InterSystemsFor Keyword, enter iFor URL, enter one of:http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.SearchPageZen.cls?KeyWord=%s - for Ensemble docshttp://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.SearchPageZen.cls?KeyWord=%s - for InterSystems Iris docsClick DoneAfter that you'll be able to search InterSystems documentation in Chrome address bar by typing: i keyword and pressing Enter. Cool!That works, thank you!
Announcement
Evgeny Shvarov · Mar 17, 2017

InterSystems Technology Student Contest 2017

Hi, Community! Let me share the news about InterSystems Technology Student Contest we just have launched! The contest has two online stages: Registration and the online test stage: March 17 - April 5, 2017. Online tasks stage: April 7 -15, 2017. After that, the best participants would be invited to the International finals of the International IT-Planet Olympiad 2016/17, on May 26-29 in Sochi, Black Sea resort city in Russia. If you are a student under the age of 25 you are very welcome to participate! If you know students or professors who might be interested please share the announcement! The admission is free and limited to participants from USA and Europe. InterSystems will cover the flight and accommodation expenses for the finalists for their travel to Russia. See the additional information on the contest page. If you have any questions please comment below. I didn't see any mention of a "USA and Europe" restriction on the website information.Any reason for not including, say, Canada? Hi, John!Thanks for mentioning it.It's an experiment for us, so the list of countries is as it is on behalf of "experiment conditions". UK is included), so the students from UK are very welcome.
Article
Eduard Lebedyuk · Mar 24, 2017

Logging using macros in InterSystems IRIS

In my previous article, we reviewed possible use-cases for macros, so let’s now proceed to a more comprehensive example of macros usability. In this article we will design and build a logging system. Logging system Logging system is a useful tool for monitoring the work of an application that saves a lot of time during debugging and monitoring. Our system would consist of two parts: Storage class (for log records) Set of macros that automatically add a new record to the log Storage class Let’s create a table of what we need to store and specify when this data can be obtained – during compilation or at runtime. This will be required when working on the second part of the system - macros, where we will aim to have as many loggable details during compilation as possible: Information Obtained during Event type Compilation Class name Compilation Method name Compilation Arguments passed to a method Compilation Line number in the cls source code Runtime Line number in the generated int code Runtime Username Runtime Date/Time Runtime Message Runtime IP address Runtime Let’s create an App.Log class containing the properties from the table above. When an App.Log object is created, User Name, Date/Time and IP address properties are filled out automatically. App.Log class: Class App.Log Extends %Persistent { /// Type of event Property EventType As %String(MAXLEN = 10, VALUELIST = ",NONE,FATAL,ERROR,WARN,INFO,STAT,DEBUG,RAW") [ InitialExpression = "INFO" ]; /// Name of class, where event happened Property ClassName As %Dictionary.Classname(MAXLEN = 256); /// Name of method, where event happened Property MethodName As %String(MAXLEN = 128); /// Line of int code Property Source As %String(MAXLEN = 2000); /// Line of cls code Property SourceCLS As %String(MAXLEN = 2000); /// Cache user Property UserName As %String(MAXLEN = 128) [ InitialExpression = {$username} ]; /// Arguments' values passed to method Property Arguments As %String(MAXLEN = 32000, TRUNCATE = 1); /// Date and time Property TimeStamp As %TimeStamp [ InitialExpression = {$zdt($h, 3, 1)} ]; /// User message Property Message As %String(MAXLEN = 32000, TRUNCATE = 1); /// User IP address Property ClientIPAddress As %String(MAXLEN = 32) [ InitialExpression = {..GetClientAddress()} ]; /// Determine user IP address ClassMethod GetClientAddress() { // %CSP.Session source is preferable #dim %request As %CSP.Request If ($d(%request)) { Return %request.CgiEnvs("REMOTE_ADDR") } Return $system.Process.ClientIPAddress() } } Logging macros Usually, macros are stored in separate *.inc files containing their definitions. The necessary files can be included into classes using the Include MacroFileName command, which in this case will look as follows: Include App.LogMacro. To start, let’s define the main macro that the user will add to their application’s code: #define LogEvent(%type, %message) Do ##class(App.Log).AddRecord($$$CurrentClass, $$$CurrentMethod, $$$StackPlace, %type, $$$MethodArguments, %message) This macro accepts two input arguments: Event Type and Message. The Message argument is defined by the user, but the Event Type parameter will require additional macros with different names that will automatically identify the event type: #define LogNone(%message) $$$LogEvent("NONE", %message) #define LogError(%message) $$$LogEvent("ERROR", %message) #define LogFatal(%message) $$$LogEvent("FATAL", %message) #define LogWarn(%message) $$$LogEvent("WARN", %message) #define LogInfo(%message) $$$LogEvent("INFO", %message) #define LogStat(%message) $$$LogEvent("STAT", %message) #define LogDebug(%message) $$$LogEvent("DEBUG", %message) #define LogRaw(%message) $$$LogEvent("RAW", %message) Therefore, in order to perform logging, the user only needs to place the $$$LogError("Additional message") macro in the application code.All we need to do now is to define the $$$CurrentClass, $$$CurrentMethod, $$$StackPlace, $$$MethodArguments macros. Let’s start with the first three: #define CurrentClass ##Expression($$$quote(%classname)) #define CurrentMethod ##Expression($$$quote(%methodname)) #define StackPlace $st($st(-1),"PLACE") %classname, %methodname variables are described in the documentation. The $stack function returns INT code line number. To convert it into CLS line number we can use this code. Let's use the %Dictionary package to get a list of method arguments and their values. It contains all the information about the classes, including method descriptions. We are particularly interested in the %Dictionary.CompiledMethod class and its FormalSpecParsed property, which is a list: $lb($lb("Name","Classs","Type(Output/ByRef)","Default value "),...) corresponding to the method signature. For example: ClassMethod Test(a As %Integer = 1, ByRef b = 2, Output c) will have the following FormalSpecParsed value: $lb( $lb("a","%Library.Integer","","1"), $lb("b","%Library.String","&","2"), $lb("c","%Library.String","*","")) We need to make $$$MethodArguments macro expand into the following code (for the Test method): "a="_$g(a,"Null")_"; b="_$g(b,"Null")_"; c="_$g(c,"Null")_";" To achieve this, we have to do the following during compilation: Get a class name and a method name Open a corresponding instance of the %Dictionary.CompiledMethod class and get its FormalSpec property Convert it into a source code line Let's add corresponding methods to the App.Log class: ClassMethod GetMethodArguments(ClassName As %String, MethodName As %String) As %String { Set list = ..GetMethodArgumentsList(ClassName,MethodName) Set string = ..ArgumentsListToString(list) Return string } ClassMethod GetMethodArgumentsList(ClassName As %String, MethodName As %String) As %List { Set result = "" Set def = ##class(%Dictionary.CompiledMethod).%OpenId(ClassName _ "||" _ MethodName) If ($IsObject(def)) { Set result = def.FormalSpecParsed } Return result } ClassMethod ArgumentsListToString(List As %List) As %String { Set result = "" For i=1:1:$ll(List) { Set result = result _ $$$quote($s(i>1=0:"",1:"; ") _ $lg($lg(List,i))_"=") _ "_$g(" _ $lg($lg(List,i)) _ ","_$$$quote(..#Null)_")_" _$s(i=$ll(List)=0:"",1:$$$quote(";")) } Return result } Let’s now define the $$$MethodArguments macro as: #define MethodArguments ##Expression(##class(App.Log).GetMethodArguments(%classname,%methodname)) Use case Next, let's create an App.Use class with a Test method to demonstrate the capabilities of the logging system: Include App.LogMacro Class App.Use [ CompileAfter = App.Log ] { /// Do ##class(App.Use).Test() ClassMethod Test(a As %Integer = 1, ByRef b = 2) { $$$LogWarn("Text") } } As a result, the $$$LogWarn("Text") macro in the int code converts into the following line: Do ##class(App.Log).AddRecord("App.Use","Test",$st($st(-1),"PLACE"),"WARN","a="_$g(a,"Null")_"; b="_$g(b,"Null")_";", "Text") Execution of this code will create a new App.Log record: Improvements Having created a logging system, here's some improvement ideas: First of all, a possibility to process object-type arguments since our current implementation only logs object oref. Second, a call to restore the context of a method from stored argument values. Processing of object-type arguments The line that puts an argument value to the log is generated in the ArgumentsListToString method and looks like this: "_$g(" _ $lg($lg(List,i)) _ ","_$$$quote(..#Null)_")_" Let's do some refactoring and move it into a separate GetArgumentValue method that will accept a variable name and class (all of which we know from FormalSpecParsed) and output a code that will convert the variable into a line. We'll use existing code for data types, and objects will be converted into JSON with the help of SerializeObject (for calling from the user code) and WriteJSONFromObject (for converting an object into JSON) methods: ClassMethod GetArgumentValue(Name As %String, ClassName As %Dictionary.CacheClassname) As %String { If $ClassMethod(ClassName, "%Extends", "%RegisteredObject") { // it's an object Return "_##class(App.Log).SerializeObject("_Name _ ")_" } Else { // it's a datatype Return "_$g(" _ Name _ ","_$$$quote(..#Null)_")_" } } ClassMethod SerializeObject(Object) As %String { Return:'$IsObject(Object) Object Return ..WriteJSONFromObject(Object) } ClassMethod WriteJSONFromObject(Object) As %String [ ProcedureBlock = 0 ] { Set OldIORedirected = ##class(%Device).ReDirectIO() Set OldMnemonic = ##class(%Device).GetMnemonicRoutine() Set OldIO = $io Try { Set Str="" //Redirect IO to the current routine - makes use of the labels defined below Use $io::("^"_$ZNAME) //Enable redirection Do ##class(%Device).ReDirectIO(1) Do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(Object) } Catch Ex { Set Str = "" } //Return to original redirection/mnemonic routine settings If (OldMnemonic '= "") { Use OldIO::("^"_OldMnemonic) } Else { Use OldIO } Do ##class(%Device).ReDirectIO(OldIORedirected) Quit Str // Labels that allow for IO redirection // Read Character - we don't care about reading rchr(c) Quit // Read a string - we don't care about reading rstr(sz,to) Quit // Write a character - call the output label wchr(s) Do output($char(s)) Quit // Write a form feed - call the output label wff() Do output($char(12)) Quit // Write a newline - call the output label wnl() Do output($char(13,10)) Quit // Write a string - call the output label wstr(s) Do output(s) Quit // Write a tab - call the output label wtab(s) Do output($char(9)) Quit // Output label - this is where you would handle what you actually want to do. // in our case, we want to write to Str output(s) Set Str = Str_s Quit } A log entry with an object-type argument looks like this: Restoring the context The idea of this method is to make all arguments available in the current context (mostly in the terminal, for debugging). To this end, we can use the ProcedureBlock method parameter. When set to 0, all variables declared within such a method will remain available upon quitting the method. Our method will open an object of the App.Log class and deserialize the Arguments property. ClassMethod LoadContext(Id) As %Status [ ProcedureBlock = 0 ] { Return:'..%ExistsId(Id) $$$OK Set Obj = ..%OpenId(Id) Set Arguments = Obj.Arguments Set List = ..GetMethodArgumentsList(Obj.ClassName,Obj.MethodName) For i=1:1:$Length(Arguments,";")-1 { Set Argument = $Piece(Arguments,";",i) Set @$lg($lg(List,i)) = ..DeserializeObject($Piece(Argument,"=",2),$lg($lg(List,i),2)) } Kill Obj,Arguments,Argument,i,Id,List } ClassMethod DeserializeObject(String, ClassName) As %String { If $ClassMethod(ClassName, "%Extends", "%RegisteredObject") { // it's an object Set st = ##class(%ZEN.Auxiliary.jsonProvider).%ConvertJSONToObject(String,,.obj) Return:$$$ISOK(st) obj } Return String } This is how it looks in the terminal: >zw >do ##class(App.Log).LoadContext(2) >zw a=1 b=<OBJECT REFERENCE>[2@%ZEN.proxyObject] >zw b b=<OBJECT REFERENCE>[2@%ZEN.proxyObject] +----------------- general information --------------- | oref value: 2 | class name: %ZEN.proxyObject | reference count: 2 +----------------- attribute values ------------------ | %changed = 1 | %data("prop1") = 123 | %data("prop2") = "abc" | %index = "" What’s next? The key potential improvement is to add another argument to the log class with an arbitrary list of variables created inside the method. Conclusions Macros can be quite useful for application development. Questions Is there a way to obtain line number during compilation? Links Part I. Macros GitHub repository Cool article Edward! Nevertheless I have a little note here. According to my experience working on a huge code base, logging with macros may turn to a big pain as soon as you decide to change (add, etc) arguments to your macro, or in other words, change the macro code. We had an application full of logging macros with something like you mentioned: #define LogFatal(%message) $$$LogEvent("FATAL", %message) And at some point the logging mechanism became working improperly and we needed to change it (yuh, this logger used TCP connections and some other stuff to behave like "universal" logger in the application). But simply changing the macros code was not the case: we needed to recompile everything (as macros is compile-time code generator)! Every class on live system which used this macro. This was not successful (even on test environment) for the reasons I don't remember to be honest. What we did instead is... We spent a few days replacing all the macro code to simple class method calls like do ##class(Logger.Log).Warn(whatever). With this approach, when you change the Warn method and recompile, everything is applied immediately. The pros I see for using macros to class methods is the thing that you can type around 15 less characters for logging, and optionally have some compile-time expressions about the logged class using macros. But the cons here is using macros itself... I believe the similar approach can be done with class methods and with the help of $stack variable. Any comments on this? Thanks! Nice article Eduard. Re: last comment. Not sure how having to change the implementation of a Macro is any less of a pain than having to change hundreds of class method arguments. If anything Macro's are a great way to abstract application wide changes with a single line of code. Granted recompilation is required, but code that can't withstand recompilation all day long has a deeper problem. My only negative about Macro's is when you can't read the code for Macro soup, less is more for me. One tip to add. I like to have auto complete work for Macros. If you precede each macro with a triple comment, then they will appear in the auto complete suggestions... /// #define LogNone(%message) $$$LogEvent("NONE", %message) /// #define LogError(%message) $$$LogEvent("ERROR", %message) /// #define LogFatal(%message) $$$LogEvent("FATAL", %message) I think the issue of compilation order specification is more at fault here. Full compile from scratch (on a new instance) or a full recompile should work without errors every time. If it's not, set DependsOn keyword strategically. Or System. 💡 This article is considered as InterSystems Data Platform Best Practice. Hi I have developed a Debug Logging system that is not quite as expansive as this version. The Debug Log class has the following properties: Classname Username CreatedTS Key Message It is called with one macro #define DebugLog(%s1,%s2,%s3,%s4) where %s1 = Username (defaults to $username) %s2 is the Key which could be a method name or some other meaningful way to locate logs from a certain area in your code %s3 is the Message which is a string of Human readable text %s4 is a %Status returned by reference (in case the code has crashed) I use it in all of my developments There is a Configuration class as well that basically determines whether logging is turned on or off depending on whether you are working in DEV or PRODUCTION. The class comes with a number of methods including a Purge method that will purge all logs older than a certain number of days. If anyone is interested in this please let me know Nigel
Announcement
Evgeny Shvarov · Jun 9, 2017

Meet InterSystems Developer Community Telegram!

Hi, Community!We are introducing Telegram Channel for Developer Community most interesting articles, discussions, events and announcements! If you already use this messenger, join the DC Channel so you can easily follow what's new on Developer Community.See how it looks:Join! The permalink to join Telegram can be found also on the right of every DC page: And... may I ask you introduce your ideas on Developer Community Telegram bot?Please share it below? Thank you in advance! Why do I have to join Telegram to see the data on Atelier?I thought joining the Community was enough? HI, Mike!You don't have to. DC Telegram Channel is introduced for those who loves Telegram mobile app as a way to communicate.It is yet another channel to deliver DC content which is already posted on DC or DC YouTube.But, we plan to introduce DC bot which can be interesting and unique for DC Telegram Channel.
Question
Evgeny Shvarov · Jun 20, 2017

InterSystems Caché Project Files and Folders

Hi, Community! How do you store the source files of your Caché project? What is the directories structure? What are the benefits? I prefer the following structure: /cls/package/subpackage/class.cls /mac/package/routine.mac /int/package/routine.int /inc/macro.inc /dfi/folder/sample.pivot.dfi Example Benefits: folders are packages.easy to understand what type of sources are in the project. What is your approach? Hi, Rubens!Thank you for the wide answer, very interesting.I agree with you on "no subfolders for routines" and separate folder for server-side code. May I wonder what are you using as IDE (Studio? Atelier? Something else?) and how do you import/export your code with Caché. I develop using a mix of Caché Studio with Visual Studio Code.I use Visual Studio Code for dealing with front-end code, while using Caché Studio for back-end.I don't use Caché Studio to edit static files. I'm actually doing experiments using my Port library for managing export/import operations. About how I keep the server code close to it's client counterpart is quite simple. By default Port exports project following the template /CacheProjects/{NAMESPACE}/{PROJECT}, so instead of depending on it, I overwrite that path to /my-repo/server.From this point exported files will follow:/my-repo/server/cls/My/Class.cls/my-repo/server/cls/Another/Deep/Package/Whatever.cls/my-repo/server/int/myroutine.int/my-repo/server/mac/myroutine.mac/my-repo/server/dfi/mydef.dfi/my-repo/server/int/myinclude.incAnd so on, for every recognized Caché file format.Now notice that I didn't said anything about static files. That's where a module bundler like Webpack is used to orchestrate the client-side workflow.Now Caché only needs to send readable data back to the SPA (preferably JSON using %CSP.REST).When the project repo reaches a milestone. I build a release to actually export the files to the path, like this: /my-repo/server/web/app/bundle-[chunkhash].js/my-repo/server/web/app/bundle-[chunkhash].cssSince [chunkhash] is unique per build, the consumer shouldn't have any issues with browser cache.Now there's an issue: the bundled files still aren't inside the CSP folder, so I need to import the project back to Studio using Port.Files are imported using UDL instead of XML. But Port always keep a project XML up-to-date along with the UDL code.As you can see I can work with Caché code AND Client-code, alternating between both editors, thus keeping their own development scope, even though their code remain inside the same repo. cls/My/Deep/Class.clsI don't think subdirectories should be applied for routines, originally routines aren't supposed to have submodules or subpackages and dots might be part of their name. Also if you need some complexity, you wouldn't prefer using routines but classes to keep things organized. I DO NOT recommend using src, unless you want to mix both back-end and front-end code. Or you want to keep the server code in a separated repository.Here is a scaffolding based on React for better understanding.my-app-project / package.json server cls mac int csp <- this is our build path, nothing should be added manually because everything is handle by the bundler. scripts / test.js build.js dev.js config / webpack.config.dev.js webpack.config.prod.js webpack.config.test.js src / components Header index.js Header.js HeaderSearchBar.js Footer index.js Footer.js FooterCopyright.js AppContainer index.js AppContainer.js containers App.js tests components Header Header.js HeaderSearchBar.js Footer Footer.js FooterCopyright.js AppContainer index.js AppContainer You can use folders to separate both client and server codes inside the same project. You can even structure your projectusing a monorepo approach if you want to keep multiple application modules together. Now since React will be using webpack's hot module reloading along with webpack-dev-middleware that builds everything within the memory, your Caché server should only work following SPA conventions and providing consumable data.There's a catch though, whenever the developer builds a new version (using webpack.config.prod), it's mandatory to delete the older bundle and import the project back to Caché to keep the source in sync on the server and the project. I think the most important part of source control file structure is mirroring package structure in Studio, since that is the view we spend the most time with. That said it looks something like this: root/ Package1/ Class.cls Class2.cls Routine.mac Include.inc Package2/ Class.cls Class2.cls Routine.mac Include.inc Additionally: Web app should be stored in a separate repository.If there's docs/dictionaries/etc, then all sources should be in a /src folder instead of repository root.All libs/modules/etc should be moved into their separate repositories each and plugged in as a submodules.Builds, if any should not be stored in a repo but rather meta-managed (releases).Commit messages and granular commit history is one of the most helpful things when analysing project history, enforce commit message style (title is the most important i.e. PART/SUBPART - thing done in a commit).
Article
Sergey Mikhailenko · Jun 2, 2020

Increasing the Security of the InterSystems IRIS DBMS

When you first start working with InterSystems IRIS, it’s a common practice to install a system with only a minimum level of security. You have to enter passwords fewer times and this makes it easier to work with development services and web applications when you're first getting acquainted. And, sometimes, minimal security is more convenient for deploying a developed project or solution. And yet there comes a moment when you need to move your project out of development, into an Internet environment that’s very likely hostile, and it needs to be tested with the maximum security settings (that is, completely locked down) before being deployed to production. And that’s what we’ll discuss in this article. For more complete coverage of DBMS security issues in InterSystems Caché, Ensemble, and IRIS, you may want to read my other article, [Recommendations on installing the InterSystems Caché DBMS for a production environment.](https://community.intersystems.com/post/recommendations-installing-intersystems-cach%C3%A9-dbms-production-environment) The security system in InterSystems IRIS is based on the concept of applying different security settings for different categories: users, roles, services, resources, privileges, and applications. ![](https://lh6.googleusercontent.com/FoNWn41ToKOKB_gj2rGOpJdSMDN9nWMh0pgjOog1KX8oLf2JntVeQ85t2FETTkT2vYmYSOCXSMi4OXcWE5kzBmEwrWYGIUQxlwr5crjJ_XplyTu2yg2m2ZgU54Skc2n2gi59pSE3) Users can be assigned roles. Users and roles can have privileges on resources — databases, services, and applications — with varying read, write, and use rights. Users and roles can also have SQL privileges on the SQL tables located in databases. # How Security Levels Differ When installing InterSystems IRIS, you can choose the security level: Minimal, Normal, or Locked Down. The levels differ in the degree of user engagement, the available roles and services, and in the configuration of authentication methods for services and applications. For more information, read the [Preparing for InterSystems Security](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCI_prepare_install#GCI_security) section of the Preparing to Install InterSystems IRIS guide. In the documentation you’ll find the tables shown below, which show the security settings for each level. You can change the settings in the system management portal interface. ## Initial User Security Settings |Security Setting | Minimal | Normal | Locked Down | |--------------------------------------------|--------------|--------------|----------------------| |Password Pattern |3.32ANP |3.32ANP |8.32ANP | |Inactive Limit |0 |90 days |90 days | |Enable _SYSTEM User |Yes |Yes |No | |Roles assigned to UnknownUser |%All |None |None | ## Initial Service Properties |Service Property |Minimal|Normal|Locked Down| |-----------------------------------|-----------|----------|------------------| |Use Permission is Public |Yes |Yes |No | |Requires Authentication |No |Yes |Yes | |Enabled Services |Most |Some |Fewest | ## Initial Enabled Settings for Services |Service | Minimal | Normal | Locked Down | |-----------------------------------|--------------|-------------|---------------------| |%Service_Bindings | Enabled | Enabled | Disabled | |*%Service_CSP | Enabled | Enabled | Enabled | |%Service_CacheDirect | Enabled | Disabled | Disabled | |%Service_CallIn | Enabled | Disabled | Disabled | |%Service_ComPort | Disabled | Disabled | Disabled | |%Service_Console | Enabled | Enabled | Enabled | |%Service_ECP | Disabled | Disabled | Disabled | |%Service_MSMActivate | Disabled | Disabled | Disabled | |%Service_Monitor | Disabled | Disabled | Disabled | |%Service_Shadow | Disabled | Disabled | Disabled | |%Service_Telnet | Disabled | Disabled | Disabled | |%Service_Terminal | Enabled | Enabled | Enabled | |%Service_WebLink | Disabled | Disabled | Disabled | *For InterSystems IRIS, %Service_CSP applies %Service_WebGateway. The services used are slightly different for different operating systems. # How You Can Improve Security For each enabled service, you need to choose the appropriate authentication methods: unauthenticated, password, Kerberos, or delegated. You also need to disable web applications that aren’t used in the system. And for web applications that are enabled, you need to select the correct authentication method: authenticated, password, Kerberos, delegated, login, or cookie. Of course, the administrator chooses the security settings for each project and solution so the project can function according to the customer's requirements. And this is always a balance between keeping the system convenient enough that users can actually get their work done, while also secure enough to keep intruders at bay. As you know, the most secure system is a disabled system. If you encounter a need to manually increase the security level of your system more than once, this is a sure sign you need to write a software module to solve these problems. In fact, InterSystems Open Exchange has a lockdown program that can help you improve security. You’ll find the source code for the program in the repository on the InterSystems [isc-apptools-lockdown](https://openexchange.intersystems.com/package/isc-apptools-lockdown) page. Here’s what the LockDown program does. ## First, it changes passwords for preinstalled users: - Admin, - CSPSystem, - IAM, - SuperUser, - UnknownUser, - _Ensemble, - _SYSTEM. ## Second, it disables all services except: - %%service_web gateway - %service_console - %service_login - %service_terminal ## Next, it sets password protection for all web applications, including: - /csp/ensdemo - /csp/samples - /csp/user - /isc/studio/usertemplates - /csp/docbook - /csp/documatic - /isc/studio/rules - /isc/studio/templates ## Finally, it sets system-wide security parameters including: - Password complexity "8.32 ANP" - Limit on user inactivity of 90 days - Audit and all security-relevant events You can install the LockDown program on your system by downloading [LockDown.cls](https://github.com/SergeyMi37/isc-apptools-lockdown/blob/master/src/cls/App/Security/LockDown.cls) from GitHub. Then, in terminal mode, enter the following: ``` USER>zn “%SYS” %SYS>do $system.OBJ.Load("/home/irisusr/LockDown.cls","ck") ``` Or you can install it using the ZPM batch manager from the public register with the following commands: ``` USER>zn “%SYS” %SYS> zpm “install isc-apptools-lockdown” ``` # Performing a Lockdown Before executing a lockdown, it’s strongly recommended that you perform a backup. The LockDown program must be executed from the %SYS area. If you don't want to change the password for all preinstalled users, leave the first parameter empty. If you want to keep the ability to edit programs and classes using IRIS Studio, Atelier, or VSCode, don’t disable the %Service_Bindings service. To ensure this works, the bindings argument must be set to 1. Here’s an example: `do ##class(App.Security.LockDown).Apply("New Password 123",.msg,1)` This module also contains a function that’s useful if the system password is compromised and you need a replacement for all preinstalled accounts without performing a lockdown. You can run it as follows: `do ##class(App.Security.LockDown).Change Password("New Password 123", "Admin,CSPSystem,IAM,SuperUser,Unknown User, _Ensemble,_SYSTEM")` Most likely, after performing the lockdown, your application or project will stop working. To fix it, you’ll need to restore some security settings to their original state. This can be done either via the management portal interface (security section) or programmatically. # Changing Your Security Settings After Lockdown After lockdown, if your web applications used authentication methods other than passwords, you’ll need to enable them. I suggest running the software module [zpm-registry-test-deployment](https://github.com/intersystems-community/zpm-registry-test-deployment/blob/master/Installer.cls), which has an example of using LockDown for the ZPM-registry project. The code that follows is applied at the end of the installation. The project was installed on IRIS with a minimal level of security. Here’s what the code had to do: - Change passwords for all preinstalled users. - Disable all services not used by this project. - Enable password protection for all applications on the system, except web applications /registry (which allows unauthorized users to get a list of packages in the registry). - Create a new user with privileges to publish new packages in the registry. This user must have write rights to the project tables in the IRISAPP database. Create a new user: ``` set tSC= ##class(App.Security.LockDown).CreateUser(pUsername, "%DB_"_Namespace, pPassword, "ZMP registry user",Namespace) If $$$ISERR(tSC) quit tSC write !,"Create user "_pUsername ``` Add privileges for a new and unauthorized user: ``` set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM.Package", "s", "UnknownUser") set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM.Package", "s", pUsername) set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM.Package_dependencies", "s", pUsername) set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "1,ZPM_Analytics.Event", "s", pUsername) set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "9,ZPM.Package_Extent", "e", pUsername) set tSC=##class(App.Security.LockDown).addSQLPrivilege(Namespace, "9,ZPM_Analytics.Event_Extent", "e", pUsername) If $$$ISERR(tSC) quit tSC write !,"Add privileges " ``` Run the LockDown program: ``` set tSC= ##class(App.Security.LockDown).Apply(NewPassSys) If $$$ISERR(tSC) quit tSC Change the settings for the web app so that an unknown user can log in: set prop("AutheEnabled")=96 set tSC=##class(Security.Applications).Modify("/registry",.prop) If $$$ISERR(tSC) quit tSC write !,"Modify /registry " Change the settings for the %service_terminal service, changing the authorization method to Operating System, Password: set name="%service_terminal" set prop("Enabled")=1 set prop("AutheEnabled")=48 ; Operating System,Password set tSC=##class(Security.Services).Modify(name,.prop) If $$$ISERR(tSC) quit tSC write !,"Modify service terminal" ``` # Wrapping Up In this article, I discussed why you might want to increase the security level of your system and how you’d do this programmatically, and I showed an example using the InterSystems LockDown program. We used a method in which we first closed down everything in the system (that is, we set the maximum security level). We then moderated the security by opening the services and applications necessary for the project to function, but only those. I'm sure there are other ways and best practices, and I’d love to hear about them as part of the discussion of this article by the community. Have you thought about the OS security issues for the instance? For example: if you do a minimal security install on Linux, many more processes run as root than if you do any other install. The only safe way to fix that is via a re-install. I'm always nervous about the idea of converting a minimal install to a more secure one because of those kinds of issues and I don't want people to think their instances are more secure than they really are. Hi Katherine.I agree with you, and the problem of minimizing processes launched from root, my module will not solve
Announcement
Andreas Dieckow · Jan 5, 2018

InterSystems Response to Meltdown and Spectre Vulnerabilities

InterSystems continuously monitors our systems for any evidence of attempts to exploit vulnerabilities such as the newly announced Meltdown and Spectre attack vectors. At this time we have seen no indications of attempts to target InterSystems systems or technology using these vulnerabilities. · InterSystems is aware of recently reported cybersecurity vulnerabilities known as Meltdown and Spectre that affect a wide range of computer processors (See US-CERT Alert TA 18-004A, Meltdown and Spectre Side-Channel Vulnerability Guidance, https://www.us-cert.gov/ncas/alerts/TA18-004A).· As is the case with other technology companies, InterSystems is examining our products to assess and understand the direct impact of the vulnerability and what appropriate actions to take to remediate any discovered risk.· Same as with many other companies, InterSystems’ corporate operations and infrastructure uses systems that are impacted by these vulnerabilities.· InterSystems is working to remediate these operational vulnerabilities as quickly as possible using the applicable patches and firmware updates as they are made available by our vendors.· InterSystems is also monitoring any performance impact caused by patches and firmware update to develop a plan of action to address if necessary.· InterSystems continues to monitor this issue and will provide updates as appropriate.