From 334721034a2b23d9c6883fa1d39bb36333231452 Mon Sep 17 00:00:00 2001
From: Honza Mach <honza.mach.ml@gmail.com>
Date: Wed, 15 Mar 2017 10:15:19 +0100
Subject: [PATCH] Moved the permission changing feature from ZenDaemon to
 BaseApp.

Now it is possible to use this feature in ZenScripts and this also fixed the issue with permissions being dropped too late (the application was fully set up and possible files and/or directories were created with wrong permissions).
---
 pyzenkit/baseapp.py   | 66 +++++++++++++++++++++++++++++++++++++++++++
 pyzenkit/zendaemon.py | 53 ----------------------------------
 setup.py              |  2 +-
 3 files changed, 67 insertions(+), 54 deletions(-)

diff --git a/pyzenkit/baseapp.py b/pyzenkit/baseapp.py
index 0acacfc..9f54c4f 100644
--- a/pyzenkit/baseapp.py
+++ b/pyzenkit/baseapp.py
@@ -24,6 +24,8 @@ usefull features including (but not limited to) following:
 
 import os
 import sys
+import pwd
+import grp
 import re
 import shutil
 import glob
@@ -148,6 +150,8 @@ class BaseApp:
     CONFIG_ACTION      = 'action'
     CONFIG_INPUT       = 'input'
     CONFIG_LIMIT       = 'limit'
+    CONFIG_USER        = 'user'
+    CONFIG_GROUP       = 'group'
     CONFIG_CFG_FILE    = 'config_file'
     CONFIG_CFG_DIR     = 'config_dir'
     CONFIG_LOG_FILE    = 'log_file'
@@ -269,6 +273,12 @@ class BaseApp:
         # Option for setting the result limit.
         argparser.add_argument('--limit', help = 'apply given limit to the result', type = int)
 
+        # Option for overriding the process UID.
+        argparser.add_argument('--user', help = 'process UID or user name')
+
+        # Option for overriding the process GID.
+        argparser.add_argument('--group', help = 'process GID or group name')
+
         # Option for overriding the name of the configuration file.
         argparser.add_argument('--config-file', help = 'name of the config file')
 
@@ -376,6 +386,8 @@ class BaseApp:
             (self.CONFIG_ACTION,      None),
             (self.CONFIG_INPUT,       None),
             (self.CONFIG_LIMIT,       None),
+            (self.CONFIG_USER,        None),
+            (self.CONFIG_GROUP,       None),
             (self.CONFIG_CFG_FILE,    os.path.join(self.paths.get(self.PATH_CFG), "{}.conf".format(self.name))),
             (self.CONFIG_CFG_DIR,     os.path.join(self.paths.get(self.PATH_CFG), "{}".format(self.name))),
             (self.CONFIG_LOG_FILE,    os.path.join(self.paths.get(self.PATH_LOG), "{}.log".format(self.name))),
@@ -602,6 +614,42 @@ class BaseApp:
         cc[self.CORE_RUNLOG_SAVE] = True
         self.config[self.CORE][self.CORE_RUNLOG] = cc
 
+        if self.config[self.CONFIG_USER]:
+            u = self.config[self.CONFIG_USER]
+            res = None
+            if not res:
+                try:
+                    res = pwd.getpwnam(u)
+                    self.config[self.CONFIG_USER] = [res[0], res[2]]
+                except:
+                    pass
+            if not res:
+                try:
+                    res = pwd.getpwuid(int(u))
+                    self.config[self.CONFIG_USER] = [res[0], res[2]]
+                except:
+                    pass
+            if not res:
+                raise ZenAppSetupException("Unknown user account '{}'".format(u))
+
+        if self.config[self.CONFIG_GROUP]:
+            g = self.config[self.CONFIG_GROUP]
+            res = None
+            if not res:
+                try:
+                    res = grp.getgrnam(g)
+                    self.config[self.CONFIG_GROUP] = [res[0], res[2]]
+                except:
+                    pass
+            if not res:
+                try:
+                    res = grp.getgrgid(int(g))
+                    self.config[self.CONFIG_GROUP] = [res[0], res[2]]
+                except:
+                    pass
+            if not res:
+                raise ZenAppSetupException("Unknown group account '{}'".format(g))
+
     def _configure_check(self):
         """
         TODO: Implement config checking mechanism.
@@ -636,6 +684,21 @@ class BaseApp:
         # Check all loaded configurations.
         self._configure_check()
 
+    def _stage_setup_privileges(self):
+        """
+        Adjust the script privileges according to the configration.
+        """
+        g = self.c(self.CONFIG_GROUP, None)
+        if g and g[1] != os.getgid():
+            cg = grp.getgrgid(os.getgid())
+            self.dbgout("[STATUS] Dropping group privileges from '{}':'{}' to '{}':'{}'".format(cg[0], cg[2], g[0], g[1]))
+            os.setgid(g[1])
+        u = self.c(self.CONFIG_USER, None)
+        if u and u[1] != os.getuid():
+            cu = pwd.getpwuid(os.getuid())
+            self.dbgout("[STATUS] Dropping user privileges from '{}':'{}' to '{}':'{}'".format(cu[0], cu[2], u[0], u[1]))
+            os.setuid(u[1])
+
     def _stage_setup_logging(self):
         """
         Setup terminal and file logging facilities.
@@ -757,6 +820,9 @@ class BaseApp:
             # Setup configurations.
             self._stage_setup_configuration()
 
+            # Setup script privileges
+            self._stage_setup_privileges()
+
             # Setup logging, if the appropriate feature is enabled.
             if self.c(self.CONFIG_LOG_FILE):
                 self._stage_setup_logging()
diff --git a/pyzenkit/zendaemon.py b/pyzenkit/zendaemon.py
index 39a269a..4b9cc8c 100644
--- a/pyzenkit/zendaemon.py
+++ b/pyzenkit/zendaemon.py
@@ -12,8 +12,6 @@ Base implementation of generic daemon.
 import os
 import re
 import sys
-import pwd
-import grp
 import json
 import time
 import copy
@@ -252,8 +250,6 @@ class ZenDaemon(pyzenkit.baseapp.BaseApp):
     CONFIG_PID_FILE       = 'pid_file'
     CONFIG_STATE_FILE     = 'state_file'
     CONFIG_UMASK          = 'umask'
-    CONFIG_USER           = 'user'
-    CONFIG_GROUP          = 'group'
     CONFIG_STATS_INTERVAL = 'stats_interval'
     CONFIG_PARALEL        = 'paralel'
 
@@ -284,8 +280,6 @@ class ZenDaemon(pyzenkit.baseapp.BaseApp):
             (self.CONFIG_PID_FILE,       os.path.join(self.paths.get(self.PATH_RUN), "{}.pid".format(self.name))),
             (self.CONFIG_STATE_FILE,     os.path.join(self.paths.get(self.PATH_RUN), "{}.state".format(self.name))),
             (self.CONFIG_UMASK,          None),
-            (self.CONFIG_USER,           None),
-            (self.CONFIG_GROUP,          None),
             (self.CONFIG_STATS_INTERVAL, 300),
             (self.CONFIG_PARALEL,        False),
         )
@@ -318,12 +312,6 @@ class ZenDaemon(pyzenkit.baseapp.BaseApp):
         # Option for overriding the default umask.
         argparser.add_argument('--umask', help = 'default file umask')
 
-        # Option for overriding the process UID.
-        argparser.add_argument('--user', help = 'process UID or user name')
-
-        # Option for overriding the process GID.
-        argparser.add_argument('--group', help = 'process GID or group name')
-
         # Option for defining processing statistics display interval.
         argparser.add_argument('--stats-interval', help = 'define processing statistics display interval')
 
@@ -400,43 +388,6 @@ class ZenDaemon(pyzenkit.baseapp.BaseApp):
         self.config[self.CORE][self.CORE_RUNLOG][self.CORE_RUNLOG_SAVE] = True
         self.config[self.CORE][self.CORE_PSTATE][self.CORE_PSTATE_SAVE] = True
 
-        if self.config[self.CONFIG_USER]:
-            u = self.config[self.CONFIG_USER]
-            res = None
-            if not res:
-                try:
-                    res = pwd.getpwnam(u)
-                    self.config[self.CONFIG_USER] = res[2]
-                except:
-                    pass
-            if not res:
-                try:
-                    res = pwd.getpwuid(int(u))
-                    self.config[self.CONFIG_USER] = res[2]
-                except:
-                    pass
-            if not res:
-                raise ZenDaemonException("Unknown user '{}'".format(u))
-
-        if self.config[self.CONFIG_GROUP]:
-            g = self.config[self.CONFIG_GROUP]
-            res = None
-            if not res:
-                try:
-                    res = grp.getgrnam(g)
-                    self.config[self.CONFIG_GROUP] = res[2]
-                except:
-                    pass
-            if not res:
-                try:
-                    res = grp.getgruid(int(g))
-                    self.config[self.CONFIG_GROUP] = res[2]
-                except:
-                    pass
-            if not res:
-                raise ZenDaemonException("Unknown group '{}'".format(g))
-
-
     def _stage_setup_custom(self):
         """
         Perform custom daemon related setup.
@@ -742,8 +693,6 @@ class ZenDaemon(pyzenkit.baseapp.BaseApp):
                 work_dir       = self.c(self.CONFIG_WORK_DIR),
                 pidfile        = self.get_fn_pidfile(),
                 umask          = self.c(self.CONFIG_UMASK),
-                uid            = self.c(self.CONFIG_USER),
-                gid            = self.c(self.CONFIG_GROUP),
                 files_preserve = logs,
                 signals        = {
                     signal.SIGHUP:  self._hnd_signal_hup,
@@ -765,8 +714,6 @@ class ZenDaemon(pyzenkit.baseapp.BaseApp):
                 work_dir       = self.c(self.CONFIG_WORK_DIR),
                 pidfile        = self.get_fn_pidfile(),
                 umask          = self.c(self.CONFIG_UMASK),
-                uid            = self.c(self.CONFIG_USER),
-                gid            = self.c(self.CONFIG_GROUP),
                 signals        = {
                     signal.SIGHUP:  self._hnd_signal_hup,
                     signal.SIGUSR1: self._hnd_signal_usr1,
diff --git a/setup.py b/setup.py
index c4ac641..0a4a8e3 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@ with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
 
 setup(
     name = 'pyzenkit',
-    version = '0.13',
+    version = '0.16',
     description = 'Python 3 script and daemon toolkit',
     long_description = long_description,
     classifiers = [
-- 
GitLab