diff --git a/CHANGELOG b/CHANGELOG index 0d6c7d86..833e33d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ + +Changes to version ? +------------------------ + +New features and improvements: + +- .NET API: added additional callbacks to control external access to the data model (required to implement RBAC) + Changes to version 1.6.1 ------------------------ diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 1980b23b..825f7709 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -22,10 +22,13 @@ */ using System; using System.Collections.Generic; +using System.Data; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Xml.Linq; using IEC61850.Common; using IEC61850.TLS; +using static System.Net.Mime.MediaTypeNames; using static IEC61850.Client.IedConnection; // IEC 61850 API for the libiec61850 .NET wrapper library @@ -2366,15 +2369,75 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setListObjectsAccessHandler(IntPtr self, IedServer_ListObjectsAccessHandler handler, IntPtr parameter); + /// + /// Set a handler to control read and write access to control blocks and logs + /// + /// IedServer + /// handler the callback handler to be used + /// a user provided parameter that is passed to the handler [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setControlBlockAccessHandler(IntPtr self, IedServer_ControlBlockAccessHandler handler, IntPtr parameter); + /// + /// Install the global read access handler + /// The read access handler will be called for every read access before the server grants access to the client + /// + /// IedServer + /// the callback function that is invoked if a client tries to read a data object + /// a user provided parameter that is passed to the callback function + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setReadAccessHandler(IntPtr self, ReadAccessHandler handler, IntPtr parameter); + + /// + /// Set a handler to control access to a dataset (create, delete, read, write, list directory) + /// + /// IedServer + /// the callback handler to be used + /// a user provided parameter that is passed to the handler [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setDataSetAccessHandler(IntPtr self, IedServer_DataSetAccessHandler handler, IntPtr parameter); + + /// + /// callback handler to control client read access to data attributes + /// User provided callback function to control MMS client read access to IEC 61850 + /// data objects.The application is to allow read access to data objects for specific clients only. + /// It can be used to implement a role based access control (RBAC). + /// + /// the logical device the client wants to access + /// the logical node the client wants to access + /// the data object the client wants to access + /// the functional constraint of the access + /// the client connection that causes the access + /// the user provided parameter + /// DATA_ACCESS_ERROR_SUCCESS if access is accepted, DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED if access is denied + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate MmsDataAccessError ReadAccessHandler(IntPtr ld, IntPtr ln, IntPtr dataObject, int fc, IntPtr connection, IntPtr parameter); + /// + /// Callback that is called when the client is calling a dataset operation (create, delete, read, write, list directory) + /// his callback is called before the IedServer_RCBEventHandler and only in case of operations (RCB_EVENT_GET_PARAMETER, RCB_EVENT_SET_PARAMETER, RCB_EVENT_ENABLE + /// + /// user provided parameter + /// client connection that is involved + /// one of the following operation types: DATASET_CREATE, DATASET_DELETE, DATASET_READ, DATASET_WRITE, DATASET_GET_DIRECTORY + /// + /// true to allow operation, false to deny operation [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_DataSetAccessHandler(IntPtr parameter, IntPtr connection, int operation, string datasetRef); + /// + /// Callback that is called when a client is invoking a read or write service to a control block or log + /// This callback can be used to control the read and write access to control blocks and logs (SGCB, LCBs, URCBs, BRCBs, GoCBs, SVCBs, logs) + /// + /// user provided parameter + /// client connection that is involved + /// the ACSI class of the object + /// the logical device of the object + /// the logical node of the object + /// the name of the object (e.g. data object name, data set name, log name, RCB name, ...) + /// the name of a sub element of an object or NULL + /// access type (read=IEC61850_CB_ACCESS_TYPE_READ or write=IEC61850_CB_ACCESS_TYPE_WRITE) + /// true to include the object in the service response, otherwise false [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_ControlBlockAccessHandler(IntPtr parameter, IntPtr connection, int acsiClass, IntPtr ld, IntPtr ln, string objectName, string subObjectName, int accessType); @@ -2693,6 +2756,47 @@ namespace IEC61850 return false; } + public delegate MmsDataAccessError InternalReadAccessHandler(LogicalDevice ld, LogicalNode ln, DataObject dataObject, FunctionalConstraint fc, ClientConnection connection, object parameter); + + private InternalReadAccessHandler internalReadAccessHandler = null; + + private object internalReadAccessHandlerParameter = null; + + private ReadAccessHandler readAccessHandler = null; + + public void SetReadAccessHandler(InternalReadAccessHandler handler, object parameter) + { + internalReadAccessHandler = handler; + internalReadAccessHandlerParameter = parameter; + + if (readAccessHandler == null) + { + readAccessHandler = new ReadAccessHandler (InternalReadHandlerImplementation); + + IedServer_setReadAccessHandler(self, readAccessHandler, IntPtr.Zero); + } + } + + private MmsDataAccessError InternalReadHandlerImplementation(IntPtr ld, IntPtr ln, IntPtr dataObject, int fc, IntPtr connection, IntPtr parameter) + { + if (internalReadAccessHandler != null && ld != IntPtr.Zero && ln != IntPtr.Zero) + { + ClientConnection con = null; + + this.clientConnections.TryGetValue(connection, out con); + + ModelNode ldModelNode = iedModel.GetModelNodeFromNodeRef(ld); + ModelNode lnModelNode = iedModel.GetModelNodeFromNodeRef(ln); + ModelNode doModelNode = iedModel.GetModelNodeFromNodeRef(dataObject); + + return internalReadAccessHandler(ldModelNode as LogicalDevice, lnModelNode as LogicalNode, doModelNode as DataObject, (FunctionalConstraint)fc, con, internalReadAccessHandlerParameter); + } + + return MmsDataAccessError.UNKNOWN; + } + + + private Dictionary writeAccessHandlers = new Dictionary (); private void ConnectionIndicationHandlerImpl (IntPtr iedServer, IntPtr clientConnection, bool connected, IntPtr parameter) diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index c6f069d8..c56a4a75 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -160,6 +160,38 @@ namespace server_access_control iedServer.SetDataSetAccessHandler(dataSetAccessHandler, iedServer); + /* Install handler to perform read access control on data model elements + * NOTE: when read access to a data model element is blocked this will also prevent the client + * to read the data model element in a data set or enable a RCB instance that uses a dataset + * containing the restricted data model element. + */ + MmsDataAccessError readAccessHandler(LogicalDevice ld, LogicalNode ln, DataObject dataObject, FunctionalConstraint fc, ClientConnection connection, object parameter) + { + if( dataObject!= null) + Console.WriteLine("Read access to "+ld.GetName() + "/"+ln.GetName() + "."+dataObject.GetName() + "\n"); + else + Console.WriteLine("Read access to "+ld.GetName() + "/"+ln.GetName() + "\n"); + + if (dataObject == null) + { + if (ln.GetName() == "GGIO1") + { + return MmsDataAccessError.OBJECT_ACCESS_DENIED; + } + } + else + { + if (ln.GetName() == "GGIO1" && dataObject.GetName() == "AnIn1") + { + return MmsDataAccessError.OBJECT_ACCESS_DENIED; + } + } + + return MmsDataAccessError.SUCCESS; + } + + iedServer.SetReadAccessHandler(readAccessHandler, null); + iedServer.Start(102);