Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow the script handler to wait for the script #1307

Merged
merged 8 commits into from Nov 15, 2018

Conversation

cornelinux
Copy link
Member

@cornelinux cornelinux commented Nov 12, 2018

Script handles can now be configured to wait for the
script to execute.
The default behaviour is to put the script in the background.

Closes #1222

Merging #1307 into master will increase coverage by 0.1%.
The diff coverage is 81.81%.
Impacted file tree graph

@@            Coverage Diff            @@
##           master    #1307     +/-   ##
=========================================
+ Coverage   95.81%   95.91%   +0.1%
=========================================
Files         142      142
Lines       17233    17839    +606
=========================================
+ Hits        16512    17111    +599
- Misses        721      728      +7
Impacted Files Coverage Δ
privacyidea/lib/config.py 94.83% <ø> (+0.02%) ⬆️
privacyidea/lib/eventhandler/scripthandler.py 81.57% <81.81%> (-5.31%) ⬇️
privacyidea/lib/log.py 34.24% <0%> (-9.59%) ⬇️
privacyidea/lib/tokens/u2f.py 94.16% <0%> (-1.67%) ⬇️
privacyidea/lib/policy.py 99.84% <0%> (+0.43%) ⬆️
privacyidea/lib/subscriptions.py 87.7% <0%> (+0.81%) ⬆️
privacyidea/lib/utils.py 97.33% <0%> (+0.88%) ⬆️
privacyidea/api/lib/postpolicy.py 97.64% <0%> (+0.99%) ⬆️
privacyidea/lib/error.py 91.91% <0%> (+1.01%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f1d0351...7bd3d27. Read the comment docs.

try:
p = subprocess.Popen(proc_args, cwd=self.script_directory)
log.info("Started script {script!r}:"
" {process!r}".format(script=script_name, process=p))
if handler_options.get("background") == SCRIPT_WAIT:
log.info("waiting for script to finish ...")
ret = p.wait()
# handle error case if ret != 0
...
except Exception as e:
log.warning("Failed to execute script {0!r}: {1!r}".format(
script_name, e))
log.warning(traceback.format_exc())

What do you think?

  • I was not aware of p.wait(). The question is, if it also raises an error accordingly?
    Then we would - if raise_error was configured - have to reraise the error.
  • You're right, we would need to re-raise an error. We could, however, just raise a privacyIDEA ServerError then.
  • The problem is, that different situations would be handled differently. p.wait() does not raise an exception in case of an exit code <> 0.
    The next problem is, that for some reason the tests do not work anymore - since the fail.sh script somehow does not work :-/
  •  File: privacyidea/lib/eventhandler/scripthandler.py:L180-206
  • Should we add an else branch like
else:
log.warning("Invalid value: {!r}".format(handler_options.get("background")))

?

  • No, but I will add an else branch, if raise_error is not set.
    Then it would be great to have this merged ;-)
  • done
  •  File: privacyidea/lib/eventhandler/scripthandler.py:L51-74
  • I think this should be self.script_directory :-)
  • True!
  • 👍
  • done
  •  File: privacyidea/lib/eventhandler/scripthandler.py:L43-74
  • Should this really be get_from_config, i.e. to read from the config database table? From the look of the option, I would think it should rather be set in pi.cfg?
  • Done in 5fc5322
  •  File: privacyidea/lib/eventhandler/scripthandler.py:L43-74
  • Please use except RuntimeError as e: since Python 3 doesn't allow the old syntax.
  • Done in bea2309
  •  File: privacyidea/lib/eventhandler/scripthandler.py:L89-108
  • What do you think about changing the values of SCRIPT_BACKGROUND and SCRIPT_WAIT to "background" and "wait", respectively? We cannot localize the phrases "Run in background" and "Wait for script to finish" anyway, which would look weird in German.
  • ok
  • Done in 5fc5322
  •  File: privacyidea/lib/eventhandler/scripthandler.py:L186-209
  • This is missing a is_true, I think
  • No. If raise error is set, then we will raise an error.
    If it is not set, we will not raise an error - right? Or am I missing something?
  • If the option is disabled, handler_options.get("raise_error") is actually the string "False", so we need to use is_true. I forgot about that too :-)
  • Thanks. Done in 83f6eaa
  •  File: privacyidea/lib/eventhandler/scripthandler.py:L186-209
  • Should we raise an error here if raise_error is True?
  • We did not raise an error before. I would like to keep it that way.
    Raising an error could result in the request to fail. But imho the script is often just an addon, which should not interfer with the functinoality of e.g. the authentication.
    Imagine an event handler, that is supposed to write a backup to a SMB mount on every 100th auth request. Now the SMB is down or someone change a password or deleted the script.
    Full denial of service of your authentication ;-)
  • But if I want to avoid that situation, I would probably set raise_error to False :-) If the script is important enough for me that I set raise_error=True, I would probably also want the request to fail if the script is deleted and cannot be executed because of that.
    Anyway, I'm also OK with keeping this as it is.
  • You are adding Feature requests in the reviews! ;-)
  • Currently "raise_error" can only be set for "background" mode.
    Should we change this?
  • Done in e2b1d60 :-)

  

Script handles can now be configured to wait for the
script to execute.
The default behaviour is to put the script in the background.

Closes #1222
@cornelinux cornelinux requested a review from a team November 12, 2018 15:50
@codecov
Copy link

codecov bot commented Nov 12, 2018

Codecov Report

Merging #1307 into master will increase coverage by 0.1%.
The diff coverage is 81.81%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master    #1307     +/-   ##
=========================================
+ Coverage   95.81%   95.91%   +0.1%     
=========================================
  Files         142      142             
  Lines       17233    17839    +606     
=========================================
+ Hits        16512    17111    +599     
- Misses        721      728      +7
Impacted Files Coverage Δ
privacyidea/lib/config.py 94.83% <ø> (+0.02%) ⬆️
privacyidea/lib/eventhandler/scripthandler.py 81.57% <81.81%> (-5.31%) ⬇️
privacyidea/lib/log.py 34.24% <0%> (-9.59%) ⬇️
privacyidea/lib/tokens/u2f.py 94.16% <0%> (-1.67%) ⬇️
privacyidea/lib/policy.py 99.84% <0%> (+0.43%) ⬆️
privacyidea/lib/subscriptions.py 87.7% <0%> (+0.81%) ⬆️
privacyidea/lib/utils.py 97.33% <0%> (+0.88%) ⬆️
privacyidea/api/lib/postpolicy.py 97.64% <0%> (+0.99%) ⬆️
privacyidea/lib/error.py 91.91% <0%> (+1.01%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f1d0351...7bd3d27. Read the comment docs.

script_name, e))
log.warning(traceback.format_exc())
if handler_options.get("raise_error"):
raise e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is mergable, but just wanted to point out potential for code deduplication here: We could get rid of the outer if-branch and instead use

            try:
                p = subprocess.Popen(proc_args, cwd=self.script_directory)
                log.info("Started script {script!r}:"
                         " {process!r}".format(script=script_name, process=p))
                if handler_options.get("background") == SCRIPT_WAIT:
                    log.info("waiting for script to finish ...")
                    ret = p.wait()
                    # handle error case if ret != 0
                    ...
            except Exception as e:
                log.warning("Failed to execute script {0!r}: {1!r}".format(
                    script_name, e))
                log.warning(traceback.format_exc())

What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not aware of p.wait(). The question is, if it also raises an error accordingly?
Then we would - if raise_error was configured - have to reraise the error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, we would need to re-raise an error. We could, however, just raise a privacyIDEA ServerError then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is, that different situations would be handled differently. p.wait() does not raise an exception in case of an exit code <> 0.

The next problem is, that for some reason the tests do not work anymore - since the fail.sh script somehow does not work :-/

script_name, e))
log.warning(traceback.format_exc())
if handler_options.get("raise_error"):
raise e

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add an else branch like

else:
    log.warning("Invalid value: {!r}".format(handler_options.get("background")))

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but I will add an else branch, if raise_error is not set.
Then it would be great to have this merged ;-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"/etc/privacyidea/scripts")
except RuntimeError:
# In case of the tests we are outside of the application context
script_directory = "tests/testdata/scripts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be self.script_directory :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"/etc/privacyidea/scripts")
except RuntimeError:
# In case of the tests we are outside of the application context
script_directory = "tests/testdata/scripts"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

try:
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY",
"/etc/privacyidea/scripts")
except RuntimeError:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use except RuntimeError as e: since Python 3 doesn't allow the old syntax.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bea2309

def __init__(self, script_directory=None):
if not script_directory:
try:
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this really be get_from_config, i.e. to read from the config database table? From the look of the option, I would think it should rather be set in pi.cfg?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5fc5322

"required": True,
"description": _("Wait for script to complete or run script in background. This will "
"either return the HTTP request early or could also block the request."),
"value": [SCRIPT_BACKGROUND, SCRIPT_WAIT]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about changing the values of SCRIPT_BACKGROUND and SCRIPT_WAIT to "background" and "wait", respectively? We cannot localize the phrases "Run in background" and "Wait for script to finish" anyway, which would look weird in German.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5fc5322

if rcode:
log.warning("Script {script!r} failed to execute with error code {error!r}".format(script=script_name,
error=rcode))
if handler_options.get("raise_error"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing a is_true, I think

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. If raise error is set, then we will raise an error.
If it is not set, we will not raise an error - right? Or am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the option is disabled, handler_options.get("raise_error") is actually the string "False", so we need to use is_true. I forgot about that too :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Done in 83f6eaa

" {process!r}".format(script=script_name, process=p))
if handler_options.get("background") == SCRIPT_WAIT:
rcode = p.wait()

except Exception as e:
log.warning("Failed to execute script {0!r}: {1!r}".format(
script_name, e))
log.warning(traceback.format_exc())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we raise an error here if raise_error is True?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did not raise an error before. I would like to keep it that way.
Raising an error could result in the request to fail. But imho the script is often just an addon, which should not interfer with the functinoality of e.g. the authentication.

Imagine an event handler, that is supposed to write a backup to a SMB mount on every 100th auth request. Now the SMB is down or someone change a password or deleted the script.
Full denial of service of your authentication ;-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if I want to avoid that situation, I would probably set raise_error to False :-) If the script is important enough for me that I set raise_error=True, I would probably also want the request to fail if the script is deleted and cannot be executed because of that.

Anyway, I'm also OK with keeping this as it is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are adding Feature requests in the reviews! ;-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently "raise_error" can only be set for "background" mode.
Should we change this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e2b1d60 :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

try:
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY",
"/etc/privacyidea/scripts")
except RuntimeError:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"/etc/privacyidea/scripts")
except RuntimeError:
# In case of the tests we are outside of the application context
script_directory = "tests/testdata/scripts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"required": True,
"description": _("Wait for script to complete or run script in background. This will "
"either return the HTTP request early or could also block the request."),
"value": [SCRIPT_BACKGROUND, SCRIPT_WAIT]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

script_name, e))
log.warning(traceback.format_exc())
if handler_options.get("raise_error"):
raise e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

script_name, e))
log.warning(traceback.format_exc())
if handler_options.get("raise_error"):
raise e

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

def __init__(self, script_directory=None):
if not script_directory:
try:
self.script_directory = get_from_config("PI_SCRIPT_HANDLER_DIRECTORY",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if rcode:
log.warning("Script {script!r} failed to execute with error code {error!r}".format(script=script_name,
error=rcode))
if handler_options.get("raise_error"):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@fredreichbier fredreichbier merged commit f21955e into master Nov 15, 2018
@fredreichbier fredreichbier deleted the 1222/wait-for-script branch November 15, 2018 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants