diff --git a/docs/source/pyotrs.txt b/docs/source/pyotrs.txt
index 34daca566c79803668aa722d849fe6e596f60e1f..b173d7fed3c3191822ada1634c14761d8314b02f 100644
--- a/docs/source/pyotrs.txt
+++ b/docs/source/pyotrs.txt
@@ -55,6 +55,17 @@ PyOTRS Lib (all)
     :noindex:
 
 
+:mod:`pyotrs.lib.SessionIDStore`
+--------------------------------
+
+.. autoclass:: pyotrs.lib.SessionIDStore
+    :members:
+    :private-members:
+    :undoc-members:
+    :show-inheritance:
+    :noindex:
+
+
 :mod:`pyotrs.lib.Ticket`
 ------------------------
 
diff --git a/docs/source/pyotrs_public.txt b/docs/source/pyotrs_public.txt
index 9dfc1b7693dee934a6d8f1c11f96d5e340b9d1eb..79190f1c3da6d513badd748d39710ef1cda0cea8 100644
--- a/docs/source/pyotrs_public.txt
+++ b/docs/source/pyotrs_public.txt
@@ -50,6 +50,15 @@ PyOTRS Lib (public)
     :noindex:
 
 
+:mod:`pyotrs.lib.SessionIDStore`
+--------------------------------
+
+.. autoclass:: pyotrs.lib.SessionIDStore
+    :members:
+    :undoc-members:
+    :show-inheritance:
+    :noindex:
+
 
 :mod:`pyotrs.lib.Ticket`
 ------------------------
diff --git a/pyotrs/lib.py b/pyotrs/lib.py
index 39ed0d4399979304c060ad9be5debf18f7ac470c..23c5500767b2e04183e13efae8cb61c9f4175c00 100644
--- a/pyotrs/lib.py
+++ b/pyotrs/lib.py
@@ -52,6 +52,14 @@ class ResponseJSONParseError(PyOTRSError):
     pass
 
 
+class SessionIDStoreNoFileError(PyOTRSError):
+    pass
+
+
+class SessionIDStoreNoTimeoutError(PyOTRSError):
+    pass
+
+
 class SessionError(PyOTRSError):
     pass
 
@@ -460,14 +468,139 @@ class Ticket(object):
 
 
 class SessionIDStore(object):
-    """Session ID: persistently store to and retrieve from to file"""
-    def __init__(self, file_path=None):
+    """Session ID: persistently store to and retrieve from to file
+
+    Args:
+        file_path (unicode): Path on disc
+        session_timeout (int): OTRS Session Timeout Value (to avoid reusing outdated session id
+        session_id_value (unicode): A Session ID as str
+        session_id_created (int): seconds as epoch when a session_id record was created
+        session_id_expires (int): seconds as epoch when a session_id record expires
+
+    Raises:
+        SessionIDStoreNoFileError
+        SessionIDStoreNoTimeoutError
+
+    .. todo::
+        Is session_timeout correct here?! Or should this just return "expires" value and caller
+        needs to decide whether to use it..?!
+
+    """
+    def __init__(self, file_path=None, session_timeout=None):
+        if not file_path:
+            raise SessionIDStoreNoFileError("Argument file_path is required!")
+
+        if not session_timeout:
+            raise SessionIDStoreNoTimeoutError("Argument session_timeout is required!")
+
         self.file_path = file_path
+        self.session_timeout = session_timeout
+        self.session_id_value = None
+        self.session_id_created = None
+        self.session_id_expires = None
 
     def __repr__(self):
-        return "<{0}>".format(self.__class__.__name__)
+        return "<{0}: {1}>".format(self.__class__.__name__, self.file_path)
+
+    def read(self):
+        """Retrieve a stored Session ID from file
+
+        Returns:
+            Union(str, None): Retrieved Session ID or None (if none could be read)
+        """
+        if not os.path.isfile(self.file_path):
+            return None
+
+        if not SessionIDStore._validate_file_owner_and_permissions(self.file_path):
+            return None
+
+        with open(self.file_path, "r") as f:
+            content = f.read()
+        try:
+            data = json.loads(content)
+            self.session_id_value = data['session_id']
+
+            self.session_id_created = datetime.datetime.utcfromtimestamp(int(data['created']))
+            self.session_id_expires = (self.session_id_created +
+                                       datetime.timedelta(minutes=self.session_timeout))
+
+            if self.session_id_expires > datetime.datetime.utcnow():
+                return self.session_id_value  # still valid
+        except ValueError:
+            return None
+        except KeyError:
+            return None
+        except Exception as err:
+            raise Exception("Exception Type: {0}: {1}".format(type(err), err))
+
+    def write(self):
+        """Write and store a Session ID to file (rw for user only)
+
+        Returns:
+            bool: True if successful, False otherwise.
+
+        .. todo::
+            (RH) Error Handling and return True/False
+        """
+
+        if os.path.isfile(self.file_path):
+            if not SessionIDStore._validate_file_owner_and_permissions(self.file_path):
+                raise IOError("File exists but is not ok (wrong owner/permissions)!")
+
+        with open(self.file_path, 'w') as f:
+            f.write(json.dumps({'created': str(int(time.time())),
+                                'session_id': self.session_id_value}))
+        os.chmod(self.file_path, 384)  # 384 is '0600'
+
+        # TODO 2016-04-23 (RH): check this
+        if not SessionIDStore._validate_file_owner_and_permissions(self.file_path):
+            raise IOError("Race condition: Something happened to file during the run!")
+
+        return True
+
+    def delete(self):
+        """remove session id file (e.g. when it only contains an invalid session id
+
+        Raises:
+            NotImplementedError
+
+        Returns:
+            bool: True if successful, False otherwise.
+
+        .. todo::
+            (RH) implement this _remove_session_id_file
+        """
+        raise NotImplementedError("Not yet done")
+
+    @staticmethod
+    def _validate_file_owner_and_permissions(full_file_path):
+        """validate SessionIDStore file ownership and permissions
+
+        Args:
+            full_file_path (unicode): full path to file on disk
+
+        Returns:
+            bool: True if valid and correct, otherwise False
+
+        """
+        if not os.path.isfile(full_file_path):
+            raise IOError("Does not exist or not a file: {0}".format(full_file_path))
+
+        file_lstat = os.lstat(full_file_path)
+        if not file_lstat.st_uid == os.getuid():
+            return False
+
+        if not file_lstat.st_mode & 0o777 == 384:
+            """ check for unix permission User R+W only (0600)
+            >>> oct(384)
+            '0600' Python 2
+            >>> oct(384)
+            '0o600'  Python 3  """
+            return False
+
+        return True
+
 
-    
 class Client(object):
     """PyOTRS Client class - includes Session handling
 
@@ -498,16 +631,18 @@ class Client(object):
             raise NoWebServiceName("Missing WebServiceName (e.g. GenericTicketConnectorREST)")
         self.webservicename = webservicename
 
-        if not session_id_file:
-            self.session_id_file = "/tmp/.session_id.tmp"
-        else:
-            self.session_id_file = session_id_file
-
         if not session_timeout:
             self.session_timeout = 28800  # 8 hours is OTRS default
         else:
             self.session_timeout = session_timeout
 
+        if not session_id_file:
+            self.session_id_store = SessionIDStore(file_path="/tmp/.session_id.tmp",
+                                                   session_timeout=self.session_timeout)
+        else:
+            self.session_id_store = SessionIDStore(file_path=session_id_file,
+                                                   session_timeout=self.session_timeout)
+
         self.https_verify = https_verify
 
         # dummy initialization
@@ -602,24 +737,26 @@ class Client(object):
             bool: True if successful, False otherwise.
         """
         # try to read session_id from file
-        if self._read_session_id_from_file():
+        self.session_id = self.session_id_store.read()
+
+        if self.session_id:
             # got one.. check whether it's still valid
             try:
                 if self.session_check_is_valid():
-                    print("Using valid Session ID from ({0})".format(self.session_id_file))
+                    print("Using valid Session ID from ({0})".format(self.session_id_store))
                     return True
             except OTRSAPIError:
-                """most likely invalid session_id so pass. Remove clear session_id_file.."""
+                """most likely invalid session_id so pass. Remove clear session_id_store.."""
 
         # got no (valid) session_id from file.. try to create new one
         if not self.session_create():
             raise SessionCreateError("Failed to create a Session ID!")
 
         # safe new created session_id to file
-        if not self._write_session_id_to_file():
+        if not self.session_id_store.write():
             raise SessionIDFileError("Failed to save Session ID to file!")
         else:
-            print("Saved new Session ID to file: {0}".format(self.session_id_file))
+            print("Saved new Session ID to file: {0}".format(self.session_id_store))
             return True
 
     def _session_create_json(self, url, payload):
@@ -646,60 +783,6 @@ class Client(object):
 
         return True
 
-    def _read_session_id_from_file(self):
-        """Retrieve a stored Session ID from file
-
-        Returns:
-            bool: True if successful, False otherwise.
-        """
-        if os.path.isfile(self.session_id_file):
-            with open(self.session_id_file, "r") as f:
-                content = f.read()
-            try:
-                data = json.loads(content)
-                created = datetime.datetime.utcfromtimestamp(int(data['created']))
-
-                expires = created + datetime.timedelta(minutes=self.session_timeout)
-                if expires > datetime.datetime.utcnow():
-                    self.session_id = data['session_id']  # still valid
-                    return True
-            except ValueError:
-                return False
-            except KeyError:
-                return False
-            except Exception as err:
-                raise Exception("Exception Type: {0}: {1}".format(type(err), err))
-
-        return False
-
-    def _write_session_id_to_file(self):
-        """Write and store a Session ID to file (rw for user only)
-
-        Returns:
-            bool: True if successful, False otherwise.
-
-        .. todo::
-            (RH) Error Handling and return True/False
-        """
-        with os.fdopen(os.open(self.session_id_file, os.O_WRONLY | os.O_CREAT, 0o600), 'w') as f:
-            f.write(json.dumps({'created': str(int(time.time())),
-                                'session_id': self.session_id}))
-            return True
-
-    def _remove_session_id_file(self):
-        """remove session id file (e.g. when it only contains an invalid session id
-
-        Raises:
-            NotImplementedError
-
-        Returns:
-            bool: True if successful, False otherwise.
-
-        .. todo::
-            (RH) implement this _remove_session_id_file
-        """
-        raise NotImplementedError("Not yet done")
-
     """
     GenericInterface::Operation::Ticket::TicketCreate
         Methods (public):
diff --git a/tests/test_client.py b/tests/test_client.py
index 17f44f962620ae8c8dfe265209cb6e1f1d08ee9b..042cc6b47626adfc7e48ae8c74cc8dca73a6c457 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -16,10 +16,11 @@ import datetime
 import requests  # noqa
 
 from pyotrs import Client
+from pyotrs import Article, Attachment, Client, DynamicField, Ticket  # noqa
+
 from pyotrs.lib import NoBaseURL, NoWebServiceName, ResponseJSONParseError
 from pyotrs.lib import SessionError
 
-from pyotrs import Article, Attachment, Client, DynamicField, Ticket  # noqa
 from pyotrs.lib import NoBaseURL, NoWebServiceName, NoCredentials  # noqa
 from pyotrs.lib import SessionCreateError, SessionIDFileError, OTRSAPIError, OTRSHTTPError  # noqa
 from pyotrs.lib import TicketError, TicketSearchNumberMultipleResults  # noqa
@@ -45,9 +46,24 @@ class ClientTests(unittest.TestCase):
     def test_init_no_webservice(self):
         self.assertRaisesRegex(NoWebServiceName, 'Missing WebSe.*', Client, 'http://localhost', '')
 
-    def test_init_session_id_file(self):
+    def test_init_session_id_store(self):
         client = Client(baseurl="http://localhost/", webservicename="foo", session_id_file=".sid")
-        self.assertEqual(client.session_id_file, '.sid')
+        self.assertEqual(client.session_id_store.file_path, '.sid')
+
+    def test_init_session_id_store_timeout_default(self):
+        client = Client(baseurl="http://localhost/",
+                        webservicename="foo",
+                        session_id_file=".sid")
+        self.assertEqual(client.session_id_store.file_path, '.sid')
+        self.assertEqual(client.session_id_store.session_timeout, 28800)
+
+    def test_init_session_id_store_timeout(self):
+        client = Client(baseurl="http://localhost/",
+                        webservicename="foo",
+                        session_id_file=".sid",
+                        session_timeout=815)
+        self.assertEqual(client.session_id_store.file_path, '.sid')
+        self.assertEqual(client.session_id_store.session_timeout, 815)
 
     def test_session_check_is_valid_no_session_id_error(self):
         """Test"""
@@ -113,7 +129,7 @@ class ClientTests(unittest.TestCase):
         self.assertDictEqual(result, {'a': 'response'})
 
     @mock.patch('pyotrs.Client.session_check_is_valid', autospec=True)
-    @mock.patch('pyotrs.Client._read_session_id_from_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.read', autospec=True)
     def test_session_restore_or_set_up_new_with_file_nok(self, mock_read_s_id, mock_is_valid):
         """Tests session_restore_or_set_up_new when read from file successful but session nok"""
         # create object
@@ -122,7 +138,7 @@ class ClientTests(unittest.TestCase):
 
         otrs_api_error = OTRSHTTPError("Failed to access OTRS. Check Hostname...")
 
-        mock_read_s_id.return_value = True
+        mock_read_s_id.return_value = "SomeSessionID1"
         mock_is_valid.side_effect = otrs_api_error
 
         self.assertRaisesRegex(OTRSHTTPError,
@@ -133,14 +149,14 @@ class ClientTests(unittest.TestCase):
         self.assertEqual(mock_is_valid.call_count, 1)
 
     @mock.patch('pyotrs.Client.session_check_is_valid', autospec=True)
-    @mock.patch('pyotrs.Client._read_session_id_from_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.read', autospec=True)
     def test_session_restore_or_set_up_new_with_file_ok(self, mock_read_s_id, mock_is_valid):
         """Tests session_restore_or_set_up_new when read from file successful and session ok"""
         # create object
         obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
         obj.session_id = "foobar2"
 
-        mock_read_s_id.return_value = True
+        mock_read_s_id.return_value = "SomeSessionID2"
         mock_is_valid.return_value = True
 
         result = obj.session_restore_or_set_up_new()
@@ -151,7 +167,7 @@ class ClientTests(unittest.TestCase):
 
     @mock.patch('pyotrs.Client.session_create', autospec=True)
     @mock.patch('pyotrs.Client.session_check_is_valid', autospec=True)
-    @mock.patch('pyotrs.Client._read_session_id_from_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.read', autospec=True)
     def test_session_restore_or_set_up_new_with_file_pass(self,
                                                           mock_read_s_id,
                                                           mock_is_valid,
@@ -161,7 +177,7 @@ class ClientTests(unittest.TestCase):
         obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
         obj.session_id = "foobar"
 
-        mock_read_s_id.return_value = True
+        mock_read_s_id.return_value = "SomeSessionID"
         mock_is_valid.side_effect = OTRSHTTPError("Failed to access OTRS. Check Hostname...")
         mock_s_create.return_value = True
 
@@ -174,14 +190,14 @@ class ClientTests(unittest.TestCase):
         self.assertEqual(mock_is_valid.call_count, 1)
 
     @mock.patch('pyotrs.Client.session_create', autospec=True)
-    @mock.patch('pyotrs.Client._read_session_id_from_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.read', autospec=True)
     def test_session_restore_or_set_up_new_nok(self, mock_read_s_id, mock_s_create):
         """Tests session_restore_or_set_up_new no file; create unsuccessful"""
         # create object
         obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
         obj.session_id = "foobar"
 
-        mock_read_s_id.return_value = False
+        mock_read_s_id.return_value = None
         mock_s_create.return_value = False
 
         self.assertRaisesRegex(SessionCreateError,
@@ -191,16 +207,16 @@ class ClientTests(unittest.TestCase):
         self.assertEqual(mock_read_s_id.call_count, 1)
         self.assertEqual(mock_s_create.call_count, 1)
 
-    @mock.patch('pyotrs.Client._write_session_id_to_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.write', autospec=True)
     @mock.patch('pyotrs.Client.session_create', autospec=True)
-    @mock.patch('pyotrs.Client._read_session_id_from_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.read', autospec=True)
     def test_session_restore_or_set_up_new_ok_no_wr(self, mock_read_s_id, mock_s_create, mock_wr):
         """Tests session_restore_or_set_up_new no file; create successful; write (wr) not ok"""
         # create object
         obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
         obj.session_id = "foobar"
 
-        mock_read_s_id.return_value = False
+        mock_read_s_id.return_value = None
         mock_s_create.return_value = True
         mock_wr.return_value = False
 
@@ -212,16 +228,16 @@ class ClientTests(unittest.TestCase):
         self.assertEqual(mock_s_create.call_count, 1)
         self.assertEqual(mock_wr.call_count, 1)
 
-    @mock.patch('pyotrs.Client._write_session_id_to_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.write', autospec=True)
     @mock.patch('pyotrs.Client.session_create', autospec=True)
-    @mock.patch('pyotrs.Client._read_session_id_from_file', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore.read', autospec=True)
     def test_session_restore_or_set_up_new_ok_wr_ok(self, mock_read_s_id, mock_s_create, mock_wr):
         """Tests session_restore_or_set_up_new no file; create successful; write (wr) ok"""
         # create object
         obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
         obj.session_id = "foobar"
 
-        mock_read_s_id.return_value = False
+        mock_read_s_id.return_value = None
         mock_s_create.return_value = True
         mock_wr.return_value = True
 
@@ -231,146 +247,6 @@ class ClientTests(unittest.TestCase):
         self.assertEqual(mock_s_create.call_count, 1)
         self.assertEqual(mock_wr.call_count, 1)
 
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_no_file(self, mock_isfile):
-        """Tests _read_session_id_from_file no file"""
-        # create object
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = False
-
-        self.assertFalse(obj._read_session_id_from_file(), False)
-        self.assertEqual(mock_isfile.call_count, 1)
-        mock_isfile.assert_called_with("/tmp/.sid")
-
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_w_file_valid_json(self, mock_isfile):
-        """Tests _read_session_id_from_file with a file with valid json but in past"""
-        # create object
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = True
-
-        with mock.patch('pyotrs.lib.open',
-                        mock.mock_open(read_data='{"created": "1", '
-                                                 '"session_id": "foo2"}')) as m:
-            obj._read_session_id_from_file()
-
-        m.assert_called_once_with('/tmp/.sid', 'r')
-        self.assertNotEqual(obj.session_id, 'foo2')
-
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_valid_json_in_no_created(self, mock_isfile):
-        """Tests _read_session_id_from_file with a file with valid json but no created"""
-        # create object TODO: not working
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = True
-
-        with mock.patch('pyotrs.lib.open',
-                        mock.mock_open(read_data='{"not_epoch_time": "2147483646", '
-                                                 '"no_s_id": "fake"}')) as m:
-            result = obj._read_session_id_from_file()
-            print(result)
-            """
-            self.assertRaisesRegex(Exception,
-                                   'Some Exception.*',
-                                   obj._read_session_id_from_file)
-            """
-        m.assert_called_once_with('/tmp/.sid', 'r')
-
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_valid_json_sid_in_p(self, mock_isfile):
-        """Tests _read_session_id_from_file with a file with valid json and sid in past"""
-        # create object
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = True
-
-        with mock.patch('pyotrs.lib.open',
-                        mock.mock_open(read_data='{"created": "1", '
-                                                 '"session_id": "foo3"}')) as m:
-            result = obj._read_session_id_from_file()
-
-        m.assert_called_once_with('/tmp/.sid', 'r')
-        self.assertEqual(result, False)
-        self.assertNotEqual(obj.session_id, 'foo3')
-
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_valid_json_sid_in_f(self, mock_isfile):
-        """Tests _read_session_id_from_file with a file with valid json and sid in future"""
-        # create object
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = True
-
-        with mock.patch('pyotrs.lib.open',
-                        mock.mock_open(read_data='{"created": "2147483646", '
-                                                 '"session_id": "foo4"}')) as m:
-            result = obj._read_session_id_from_file()
-
-        m.assert_called_once_with('/tmp/.sid', 'r')
-        self.assertEqual(result, True)
-        self.assertEqual(obj.session_id, 'foo4')
-
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_valid_json_in_f_no_sid(self, mock_isfile):
-        """Tests _read_session_id_from_file with a file with valid json and in future but no sid"""
-        # create object TODO: not working
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = True
-
-        with mock.patch('pyotrs.lib.open',
-                        mock.mock_open(read_data='{"created": "2147483646", '
-                                                 '"no_s_id": "fake"}')) as m:
-            result = obj._read_session_id_from_file()
-
-        self.assertEqual(result, False)
-        m.assert_called_once_with('/tmp/.sid', 'r')
-
-    @mock.patch('pyotrs.lib.json.loads', autospec=True)
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_w_file_val_exception(self, mock_isfile, mock_json_loads):
-        """Tests _read_session_id_from_file with a file with an ValueError exception"""
-        # create object
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = True
-        mock_json_loads.side_effect = ValueError("Some value exception")
-
-        with mock.patch('pyotrs.lib.open',
-                        mock.mock_open(read_data='invalid_json')) as m:
-            result = obj._read_session_id_from_file()
-        self.assertEqual(result, False)
-        m.assert_called_once_with('/tmp/.sid', 'r')
-
-    @mock.patch('pyotrs.lib.json.loads', autospec=True)
-    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
-    def test__read_session_id_from_file_w_file_unc_exception(self, mock_isfile, mock_json_loads):
-        """Tests _read_session_id_from_file with a file with an uncaught exception"""
-        # create object
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        obj.session_id_file = "/tmp/.sid"
-
-        mock_isfile.return_value = True
-        mock_json_loads.side_effect = Exception("Some exception")
-
-        with mock.patch('pyotrs.lib.open',
-                        mock.mock_open(read_data='invalid_json')) as m:
-            self.assertRaisesRegex(Exception,
-                                   'Exception Type',
-                                   obj._read_session_id_from_file)
-
-        m.assert_called_once_with('/tmp/.sid', 'r')
-
     @mock.patch('pyotrs.Client._ticket_get_json', autospec=True)
     def test_ticket_get_by_id_fail(self, mock_ticket_get_json):
         """Tests ticket_get_by_id fail"""
@@ -445,20 +321,6 @@ class ClientTests(unittest.TestCase):
 
         self.assertEqual(mock_ticket_search.call_count, 1)
 
-    def test__write_session_id_to_file(self):
-        """Tests _write_session_id_to_file not implemented"""
-        # create object
-        Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        # TODO
-
-    def test__remove_session_id_file(self):
-        """Tests_remove_session_id_file not implemented"""
-        # create object
-        obj = Client(baseurl="http://fqdn", webservicename="GenericTicketConnectorREST")
-        self.assertRaisesRegex(NotImplementedError,
-                               'Not yet done',
-                               obj._remove_session_id_file)
-
     def test_ticket_create_no_ticket(self):
         """Test ticket_create - no ticket specified"""
         obj = Client(baseurl="http://fqdn",
diff --git a/tests/test_session_id_store.py b/tests/test_session_id_store.py
index d476186349cc165ee3670fd73036239250eac793..4d4115091485edbc94813f3218c0c4062e44af34 100644
--- a/tests/test_session_id_store.py
+++ b/tests/test_session_id_store.py
@@ -12,33 +12,346 @@ from __future__ import unicode_literals  # support both Python2 and 3
 import unittest2 as unittest
 import mock
 
-from pyotrs import SessionIDStore
+from pyotrs.lib import SessionIDStore
+from pyotrs.lib import SessionIDStoreNoFileError, SessionIDStoreNoTimeoutError
 
 
 class SessionIDStoreTests(unittest.TestCase):
-    def test_init(self):
-        sis = SessionIDStore()
+    def test_init_no_file(self):
+        self.assertRaisesRegex(SessionIDStoreNoFileError,
+                               'Argument file_path is required!',
+                               SessionIDStore)
+
+    def test_init_no_timeout(self):
+        self.assertRaisesRegex(SessionIDStoreNoTimeoutError,
+                               'Argument session_timeout is required!',
+                               SessionIDStore,
+                               file_path='/tmp/.foo.bar')
+
+    def test_init_ok(self):
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
         self.assertIsInstance(sis, SessionIDStore)
 
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_no_file(self, mock_isfile):
+        """Tests _read_session_id_from_file no file"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = False
+        result = sis.read()
+
+        self.assertIsNone(result)
+        self.assertEqual(mock_isfile.call_count, 1)
+        mock_isfile.assert_called_with("/tmp/.session_id_store")
+
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_w_invalid_file(self, mock_isfile, mock_validation):
+        """Tests read with an invalid file"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = False
+
+        result = sis.read()
+
+        self.assertIsNone(result)
+        self.assertEqual(mock_isfile.call_count, 1)
+        self.assertEqual(mock_validation.call_count, 1)
+
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_w_file_valid_json(self, mock_isfile, mock_validation):
+        """Tests read with a file with valid json but in past"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = True
+        r_data = '{"created": "1", "session_id": "foo2"}'
+
+        with mock.patch('pyotrs.lib.open',
+                        mock.mock_open(read_data=r_data)) as m:
+                        result = sis.read()
+
+        m.assert_called_once_with('/tmp/.session_id_store', 'r')
+        self.assertNotEqual(result, 'foo2')
+
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_valid_json_sid_in_p(self, mock_isfile, mock_validation):
+        """Tests _read_session_id_from_file with a file with valid json and sid in past"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = True
+        r_data = '{"created": "1", "session_id": "foo3"}'
+
+        with mock.patch('pyotrs.lib.open',
+                        mock.mock_open(read_data=r_data)) as m:
+            result = sis.read()
+
+        m.assert_called_once_with('/tmp/.session_id_store', 'r')
+        self.assertIsNone(result)
+
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_valid_json_sid_in_f(self, mock_isfile, mock_validation):
+        """Tests _read_session_id_from_file with a file with valid json and sid in future"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = True
+        r_data = '{"created": "2147483646", "session_id": "foo4"}'
+
+        with mock.patch('pyotrs.lib.open',
+                        mock.mock_open(read_data=r_data)) as m:
+            result = sis.read()
+
+        m.assert_called_once_with('/tmp/.session_id_store', 'r')
+        self.assertEqual(result, 'foo4')
+
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_valid_json_in_f_no_sid(self, mock_isfile, mock_validation):
+        """Tests _read_session_id_from_file with a file with valid json and in future but no sid"""
+        # create object TODO: not working
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = True
+        r_data = '{"created": "2147483646", "no_s_id": "fake"}'
+
+        with mock.patch('pyotrs.lib.open',
+                        mock.mock_open(read_data=r_data)) as m:
+            result = sis.read()
+
+        self.assertIsNone(result)
+        m.assert_called_once_with('/tmp/.session_id_store', 'r')
+
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.json.loads', autospec=True)
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_w_file_val_exception(self, mock_isfile, mock_json_loads, mock_validation):
+        """Tests _read_session_id_from_file with a file with an ValueError exception"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = True
+        mock_json_loads.side_effect = ValueError("Some value exception")
+        r_data = 'invalid_json'
+
+        with mock.patch('pyotrs.lib.open',
+                        mock.mock_open(read_data=r_data)) as m:
+            result = sis.read()
+        self.assertIsNone(result)
+        m.assert_called_once_with('/tmp/.session_id_store', 'r')
+
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.json.loads', autospec=True)
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_read_w_file_unc_exception(self, mock_isfile, mock_json_loads, mock_validation):
+        """Tests _read_session_id_from_file with a file with an uncaught exception"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = True
+        mock_json_loads.side_effect = Exception("Some exception")
+        r_data = 'invalid_json'
+
+        with mock.patch('pyotrs.lib.open',
+                        mock.mock_open(read_data=r_data)) as m:
+            self.assertRaisesRegex(Exception,
+                                   'Exception Type',
+                                   sis.read)
+
+        m.assert_called_once_with('/tmp/.session_id_store', 'r')
+
+    # -- write
+    @mock.patch('pyotrs.lib.os.chmod', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_write_mock_open_file_exists_nok(self, mock_isfile, mock_validation, mock_chmod):
+        """Tests write mock_open file exists; validation not ok"""
+        # create object
+        test_path = "/tmp/mocked_wr1"
+        sis = SessionIDStore(file_path=test_path, session_timeout=600)
+        sis.session_id_value = "some_session_id_value1"
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = False
+        mock_chmod.return_value = True
+
+        self.assertRaisesRegex(IOError,
+                               'File exists but is not ok.*',
+                               sis.write)
+
+    @mock.patch('pyotrs.lib.os.chmod', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_write_mock_open_file_exists_ok(self, mock_isfile, mock_validation, mock_chmod):
+        """Tests write mock_open file exists; validation ok"""
+        # create object
+        test_path = "/tmp/mocked_wr2"
+        sis = SessionIDStore(file_path=test_path, session_timeout=600)
+        sis.session_id_value = "some_session_id_value1"
+
+        mock_isfile.return_value = True
+        mock_validation.return_value = True
+
+        with mock.patch('pyotrs.lib.open', mock.mock_open()) as m:
+            result = sis.write()
+
+        self.assertTrue(result)
+        self.assertEqual(mock_isfile.call_count, 1)
+        self.assertEqual(mock_validation.call_count, 2)
+        self.assertEqual(mock_chmod.call_count, 1)
+        m.assert_called_once_with(test_path, 'w')
+
+    @mock.patch('pyotrs.lib.os.chmod', autospec=True)
+    @mock.patch('pyotrs.SessionIDStore._validate_file_owner_and_permissions')
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test_write_mock_open_file_not_exist_v_nok(self, mock_isfile, mock_validation, mock_chmod):
+        """Tests write mock_open file does not exist; validation also not ok -> Race Condition?!"""
+        # create object
+        test_path = "/tmp/mocked_wr2"
+        sis = SessionIDStore(file_path=test_path, session_timeout=600)
+        sis.session_id_value = "some_session_id_value1"
+
+        mock_isfile.return_value = False
+        mock_validation.return_value = False
+
+        with mock.patch('pyotrs.lib.open', mock.mock_open()) as m:
+            self.assertRaisesRegex(IOError,
+                                   'Race condition.*',
+                                   sis.write)
+
+        self.assertEqual(mock_isfile.call_count, 1)
+        self.assertEqual(mock_validation.call_count, 1)
+        self.assertEqual(mock_chmod.call_count, 1)
+        m.assert_called_once_with(test_path, 'w')
+
+    def test_delete(self):
+        """Tests remove - not implemented"""
+        # create object
+        sis = SessionIDStore(file_path="/tmp/.session_id_store", session_timeout=600)
+        self.assertRaisesRegex(NotImplementedError,
+                               'Not yet done',
+                               sis.delete)
+
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test__validate_non_existing_file(self, mock_isfile):
+        """Tests _validate file - existing file - wrong permissions"""
+        # create object
+        test_path = "/tmp/mocked1"
+        mock_isfile.return_value = False
+
+        self.assertRaisesRegex(IOError,
+                               'Does not exist or not a '
+                               'file: /tmp/mocked1',
+                               SessionIDStore._validate_file_owner_and_permissions,
+                               full_file_path=test_path)
+
+        self.assertEqual(mock_isfile.call_count, 1)
+
+    @mock.patch('pyotrs.lib.os.lstat', autospec=True)
+    @mock.patch('pyotrs.lib.os.getuid', autospec=True)
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test__validate_existing_file_wrong_owner(self, mock_isfile, mock_getuid, mock_lstat):
+        """Tests _validate file - existing file - wrong owner"""
+        # create object
+        test_path = "/tmp/mocked2"
+
+        mock_isfile.return_value = True
+        mock_getuid.return_value = 4711
+        mock_lstat.return_value.st_uid = 815
+        mock_lstat.return_value.st_mode = 33188  # 644
+
+        result = SessionIDStore._validate_file_owner_and_permissions(full_file_path=test_path)
+
+        self.assertFalse(result)
+        self.assertEqual(mock_isfile.call_count, 1)
+        self.assertEqual(mock_getuid.call_count, 1)
+        self.assertEqual(mock_lstat.call_count, 1)
+
+    @mock.patch('pyotrs.lib.os.lstat', autospec=True)
+    @mock.patch('pyotrs.lib.os.getuid', autospec=True)
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test__validate_existing_file_wrong_permissions(self, mock_isfile, mock_getuid, mock_lstat):
+        """Tests _validate file - existing file - wrong permissions"""
+        # create object
+        test_path = "/tmp/mocked3"
+
+        mock_isfile.return_value = True
+        mock_getuid.return_value = 4712
+        mock_lstat.return_value.st_uid = 4712
+        mock_lstat.return_value.st_mode = 33188  # 644
+
+        result = SessionIDStore._validate_file_owner_and_permissions(full_file_path=test_path)
+
+        self.assertFalse(result)
+        self.assertEqual(mock_isfile.call_count, 1)
+        self.assertEqual(mock_getuid.call_count, 1)
+        self.assertEqual(mock_lstat.call_count, 1)
+
+    @mock.patch('pyotrs.lib.os.lstat', autospec=True)
+    @mock.patch('pyotrs.lib.os.getuid', autospec=True)
+    @mock.patch('pyotrs.lib.os.path.isfile', autospec=True)
+    def test__validate_existing_file_ok(self, mock_isfile, mock_getuid, mock_lstat):
+        """Tests _validate file - existing file - ok"""
+        # create object
+        test_path = "/tmp/mocked4"
+
+        mock_isfile.return_value = True
+        mock_getuid.return_value = 4713
+        mock_lstat.return_value.st_uid = 4713
+        mock_lstat.return_value.st_mode = 33152  # 0600
+
+        result = SessionIDStore._validate_file_owner_and_permissions(full_file_path=test_path)
+
+        self.assertTrue(result)
+        self.assertEqual(mock_isfile.call_count, 1)
+        self.assertEqual(mock_getuid.call_count, 1)
+        self.assertEqual(mock_lstat.call_count, 1)
+
+    # def test__validate_existing_file(self):
+    #     """Tests _validate file - existing file - wrong permissions"""
+    #     # create object
+    #     test_path = "/home/robbie/.bash_history"  # TODO (RH): not a valid test!
+    #     result = SessionIDStore._validate_file_owner_and_permissions(full_file_path=test_path)
+    #     self.assertTrue(result)
+
+    # # Non mocked: !!!
+
+    # def test__validate_file_owner_and_permissions_non_existing_file(self):
+    #     """Tests _validate file - non existing file"""
+    #     # create object
+    #     test_path = "/osirhgiosehgoisrehgioshrgiohserdiob"
+    #     self.assertRaisesRegex(IOError,
+    #                            'Does not exist or not a '
+    #                            'file: /osirhgiosehgoisrehgioshrgiohserdiob',
+    #                            SessionIDStore._validate_file_owner_and_permissions,
+    #                            full_file_path=test_path)
+
+    # def test__validate_file_owner_and_permissions_existing_file_wrong_permissions(self):
+    #     """Tests _validate file - existing file - wrong permissions"""
+    #     # create object
+    #     test_path = "/etc/passwd"  # TODO 2016-04-23 (RH): is this a valid test?!
+    #     result = SessionIDStore._validate_file_owner_and_permissions(full_file_path=test_path)
+    #     self.assertFalse(result)
     #
-    # def test_session_check_is_valid_no_session_id_error(self):
-    #     """Test"""
-    #     client = Client(baseurl="http://localhost/", webservicename="foo")
-    #     self.assertRaisesRegex(SessionError,
-    #                            'No value set for session_id!',
-    #                            client.session_check_is_valid)
-    #
-    # @mock.patch('pyotrs.Client._ticket_get_json', autospec=True)
-    # def test_session_check_is_valid_session_id(self, mock_ticket_get_json):
-    #     """Test session_check_is_valid with a given session id"""
-    #     obj = Client(baseurl="http://localhost/", webservicename="foo")
-    #     obj.session_id = "some_other_value"
-    #     mock_ticket_get_json.return_value = True
-    #
-    #     result = obj.session_check_is_valid(session_id="some_value")
-    #
-    #     self.assertEqual(obj.session_id, "some_value")
-    #     self.assertEqual(result, True)
+    # def test__validate_file_owner_and_permissions_existing_file(self):
+    #     """Tests _validate file - existing file - wrong permissions"""
+    #     # create object
+    #     test_path = "/home/robbie/.bash_history"  # TODO (RH): not a valid test!
+    #     result = SessionIDStore._validate_file_owner_and_permissions(full_file_path=test_path)
+    #     self.assertTrue(result)
 
 
 def main():