Skip to content

Commit

Permalink
Out of Band authentication.
Browse files Browse the repository at this point in the history
  • Loading branch information
nzlosh committed Jan 28, 2019
1 parent 254885a commit 7dc11d9
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 48 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [2.0.0-rc1] 2019-01-28
### Added
- setup.py to be able to install plugin from the command line.
- Dockerfile to build an image and run err-stackstorm from within a Docker container. (alpine linux based)
- Support for Out-of-Bands authentication.
- Support for formatting with XMMP backend.

### Changed
- Updated err-stacksotorm python module requirements.
Expand Down
8 changes: 4 additions & 4 deletions html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.3/semantic.min.js"></script>
<script>
console.log("err-stackstorm v0.3")
console.log("err-stackstorm v2.0")
function get_uuid_from_querystring(){
uuid = undefined;
params = window.location.href.split("?");
Expand Down Expand Up @@ -65,7 +65,7 @@
},
onFailure: function(response) {
update_message(
"Assocation failed",
"Association failed",
response.message,
"error"
);
Expand All @@ -90,7 +90,7 @@
},
onFailure: function(response) {
update_message(
"Assocation failed",
"Association failed",
response.message,
"error"
);
Expand All @@ -115,7 +115,7 @@
},
onFailure: function(response) {
update_message(
"Assocation failed",
"Association failed",
response.message,
"error"
);
Expand Down
42 changes: 25 additions & 17 deletions lib/authentication_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from lib.session_manager import SessionManager
from lib.errors import SessionInvalidError

from errbot.backends.base import Identifier
LOG = logging.getLogger(__name__)


Expand All @@ -31,11 +31,29 @@ def __init__(self, bot):
self.bot = bot
self.sessions = SessionManager(bot.cfg)

def to_userid(self, user):
"""
Convert BotIdentity, Identifier to plain text string suitable for use as the key with
Sessions and cached tokens.
param: user: may be one of BotIdentity, errbot.backend.base.Identifier or string.
"""
if isinstance(user, BotPluginIdentity):
user_id = user.name
elif isinstance(user, Identifier):
user_id = self.bot.chatbackend.normalise_user_id(user)
else:
user_id = user
LOG.info("User ID is '{}'".format(user_id))
return user_id

def pre_execution_authentication(self, chat_user):
"""
Look up the chat_user to confirm they are authenticated.
param: chat_user: the chat back end user.
return: A valid St2 Token or False in the case of an error
"""
return self.bot.cfg.auth_handler.pre_execution_authentication(self, chat_user)
user_id = self.to_userid(chat_user)
return self.bot.cfg.auth_handler.pre_execution_authentication(self, user_id)

def consume_session(self, session_id):
"""
Expand Down Expand Up @@ -85,9 +103,7 @@ def get_token_by_userid(self, user):
secret is missing.
"""
secret = False
if isinstance(user, BotPluginIdentity):
user = user.name
session = self.sessions.get_by_userid(user)
session = self.sessions.get_by_userid(self.to_userid(user))
if session:
secret = self.get_token_by_session(session.id())
return secret
Expand All @@ -109,7 +125,7 @@ def set_token_by_userid(self, user_id, token):
"""
Store StackStorm user token or api key in the secrets store.
"""
session = self.sessions.get_by_userid(user_id)
session = self.sessions.get_by_userid(self.to_userid(user_id))
if session:
ret = self.set_token_by_session(session.id(), token)
else:
Expand All @@ -120,22 +136,14 @@ def create_session(self, user, user_secret):
"""
Handle an initial request to establish a session. If a session already exists, return it.
"""
if isinstance(user, BotPluginIdentity):
user_id = user.name
else:
user_id = self.bot.chatbackend.normalise_user_id(user)

user_id = self.to_userid(user)
return self.sessions.create(user_id, user_secret)

def get_session(self, user):
"""
Returns the session associated with the user.
"""
if isinstance(user, BotPluginIdentity):
user_id = user.name
else:
user_id = self.bot.chatbackend.normalise_user_id(user)

user_id = self.to_userid(user)
session = self.sessions.get_by_userid(user_id)
if session is False:
raise SessionInvalidError
Expand Down Expand Up @@ -172,5 +180,5 @@ def associate_credentials(self, user, creds, bot_creds):
self.set_token_by_userid(user, token)
else:
LOG.warning("Failed to validate StackStorm credentials for {}.".format(user))
# Explicitly test not false to not return the value of token, just true/false if valid.
# Explicitly test not false to avoid returning tokens value.
return token is not False
12 changes: 9 additions & 3 deletions lib/authentication_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ def fetch_user_token(self, accessctl, user):
if isinstance(user, BotPluginIdentity):
user_id = user.name
else:
bot_session = self.sessions.get_by_userid(self.bot.internal_identity.name)
bot_id = accessctl.to_userid(user)
bot_session = self.sessions.get_by_userid(bot_id)
bot_token = self.sessions.get_token_by_session(bot_session)
user_id = self.bot.chatbackend.normalise_user_id(user)

Expand Down Expand Up @@ -298,10 +299,15 @@ def __init__(self, cfg, opts):

def pre_execution_authentication(self, accessctl, chat_user):
"""
TODO: Docstring
Fetch the chat user's st2 token. A valid session must exist for the token to be fetched.
accessctl: The bot's access controller used to lookup the session based on the chat user.
chat_user: The chat_user number to lookup.
"""
# TODO: FIXME: passing by reference to call itself isn't ideal, refactor!
return accessctl.get_token_by_userid(chat_user)
user_session = accessctl.get_session(chat_user)
if user_session:
user_session.is_expired()
return accessctl.get_token_by_session(user_session.id())

def authenticate_user(self, creds, bot_creds):
"""
Expand Down
2 changes: 1 addition & 1 deletion lib/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def setup(self, bot_conf, plugin_prefix):

def _configure_rbac_auth(self, bot_conf):
self.auth_handler = None
rbac_auth = bot_conf.STACKSTORM.get("rbac_auth", {"standalone": {}})
rbac_auth = bot_conf.STACKSTORM.get("rbac_auth", {"no_rbac_auth_key": {}})
for rbac_type in list(rbac_auth.keys()):
self.auth_handler = AuthHandlerFactory.instantiate(rbac_type)(
self,
Expand Down
2 changes: 1 addition & 1 deletion lib/stackstorm_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _baseurl(self, url):
return urlunparse((tmp.scheme, tmp.netloc, "", None, None, None))

def match(self, text, st2token):

LOG.debug("StackStorm Token is {}".format(st2token))
auth_kwargs = st2token.st2client()

if LOG.level <= logging.DEBUG:
Expand Down
54 changes: 33 additions & 21 deletions st2.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,22 +139,30 @@ def st2authenticate(self, msg, args):
Establish a link between the chat backend and StackStorm by authenticating over an out of
bands communication channel.
"""
ret = ""
if isinstance(self.cfg.auth_handler, ClientSideAuthHandler):
if msg.is_direct:
if len(args) > 0:
session = self.accessctl.create_session(msg.frm, args)
ret = "Your challenge response is {}".format(
self.accessctl.session_url(session.id(), "/index.html")
)
else:
ret = "Please provide a shared word to use during the authenication process."
else:
ret = "Requests for authentication in a public channel isn't possible." \
" Request authentication in a private one-to-one message."
else:
ret = "Authentication is only available when Client side authentication is configured."
return ret
if isinstance(self.cfg.auth_handler, ClientSideAuthHandler) is False:
return "Authentication is only available when Client side authentication is configured."

if msg.is_direct is not True:
return "Requests for authentication in a public channel isn't possible." \
" Request authentication in a private one-to-one message."

if len(args) < 1:
return "Please provide a shared word to use during the authenication process."

try:
session = self.accessctl.create_session(msg.frm, args)
except SessionExistsError as e:
try:
session = self.accessctl.get_session(msg.frm)
if session.is_expired() is False:
return "A valid session already exists."
except SessionExpiredError:
self.accessctl.delete_session(session.id())
session = self.accessctl.create_session(msg.frm, args)

return "Your challenge response is {}".format(
self.accessctl.session_url(session.id(), "/index.html")
)

@re_botcmd(pattern='^{} .*'.format(PLUGIN_PREFIX))
def st2_execute_actionalias(self, msg, match):
Expand All @@ -169,12 +177,17 @@ def remove_bot_prefix(msg):
return msg.replace(self.cfg.plugin_prefix, "", 1).strip()

user_id = self.chatbackend.normalise_user_id(msg.frm)
st2token = self.accessctl.pre_execution_authentication(user_id)
st2token = False
err_msg = "Failed to fetch valid credentials."
try:
st2token = self.accessctl.pre_execution_authentication(user_id)
except (SessionExpiredError, SessionInvalidError) as e:
err_msg = str(e)

if st2token is False:
rejection = "Action-Alias execution is not allowed for chat user '{}'." \
rejection = "Error: {} Action-Alias execution is not allowed for chat user '{}'." \
" Please authenticate or see your StackStorm administrator to grant access" \
".".format(user_id)
".".format(err_msg, user_id)
LOG.warning(rejection)
return rejection

Expand Down Expand Up @@ -202,8 +215,7 @@ def remove_bot_prefix(msg):
else:
result = "st2 command '{}' is disabled.".format(msg.body)
else:
result = matched_result.result #"st2 command '{}' not found. View available commands with {}st2help."
#result = result.format(msg.body, self.cfg.bot_prefix)
result = matched_result.result
return result

@arg_botcmd("--pack", dest="pack", type=str)
Expand Down

0 comments on commit 7dc11d9

Please sign in to comment.