diff --git a/Readme.md b/Readme.md index 00857c9fac3990a5a1357421f7a7c411278c03c5..4a569d41e8b9390355672baf03c3573487aec1bd 100644 --- a/Readme.md +++ b/Readme.md @@ -12,3 +12,17 @@ Issues and further documentation can be found there. ## Development A complete list of the `Minion.data` json structure can be found [here](https://lab.it.hs-hannover.de/django/salt-observer/wikis/minion-data-structure) in the wiki. + + +```python +# get output of 'w' command of all 'tgt' minions +request('token', {'tgt': '*', 'fun': 'cmd.run', 'arg': 'w'}) + +# get all grains of 'tgt' minions +request('token', {'tgt': '*', 'fun': 'grains.items'}) + +# execute some state and apply custom pillars to it +request('token', {'tgt': '*', 'fun': 'state.sls', 'kwarg': { + 'mods': 'name_of_state', 'pillar': {'some': 'pillar_data'} +}}) +``` diff --git a/salt_observer/backends.py b/salt_observer/backends.py index ab4dcee457959206c95a8e402ba39c0cacb2ecbb..8e60c604266fa8b98af3036763037e4825488ba2 100644 --- a/salt_observer/backends.py +++ b/salt_observer/backends.py @@ -1,24 +1,29 @@ from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import User -from salt_observer.cherry import SaltCherrypyApi +from salt_observer.saltapis import SaltCherrypy, SaltTornado class RestBackend(ModelBackend): ''' Authenticate against salt-api-permissions ''' - def authenticate(self, username=None, password=None): + def authenticate(self, username=None, password=None, request=None): try: - valid = SaltCherrypyApi.obtain_auth_token(username, password) - except: - valid = False + cherrypy_token = SaltCherrypy(username, password).token + tornado_token = SaltTornado(username, password).token + except Exception as e: + cherrypy_token = False + tornado_token = False - if valid: + if cherrypy_token and tornado_token: try: user = User.objects.get(username=username) except User.DoesNotExist: user = User.objects.create_user(username=username, email='', password=password) + request.session['salt_cherrypy_token'] = cherrypy_token + request.session['salt_tornado_token'] = tornado_token + return user return None diff --git a/salt_observer/cherry.py b/salt_observer/cherry.py deleted file mode 100644 index 0c6e7b79707236f3b08fb983d99df50f75fb3fd8..0000000000000000000000000000000000000000 --- a/salt_observer/cherry.py +++ /dev/null @@ -1,69 +0,0 @@ -from django.conf import settings - -import requests -from getpass import getpass - - -class SaltCherrypyApi(object): - - BASE_URL = '{protocol}://{host}:{port}'.format(**settings.SALT_API) - - def __init__(self, username, password): - ''' Log in every time an instance is created ''' - self.token = self.obtain_auth_token(username, password) - - @classmethod - def obtain_auth_token(cls, username, password): - res = requests.post(cls.BASE_URL+'/login', headers={'Accept': 'application/json'}, data={ - 'username': username, - 'password': password, - 'eauth': 'pam' - }) - - if res.status_code != 200: - raise Exception('{} - {}'.format(res.status_code, res.text)) - - return res.json().get('return')[0].get('token') - - def request(self, data, api_point=''): - data.update({'client': 'local'}) - return requests.post( - '{}/{}'.format(self.BASE_URL, api_point), - headers={ - 'Accept': 'application/json', - 'X-Auth-Token': self.token - }, - data=data - ) - - def logout(self): - return requests.post( - self.BASE_URL+'/logout', - headers={ - 'Accept': 'application/json', - 'X-Auth-Token': self.token - } - ) - - def get(self, module, target='*', api_args=[], api_kwargs={}): - return self.request({ - 'fun': module, - 'tgt': target, - 'arg': api_args, - 'kwarg': api_kwargs - }).json().get('return')[0] - - -# experimental: -# --- -# -# get output of 'w' command of all 'tgt' minions -# request('token', {'tgt': '*', 'fun': 'cmd.run', 'arg': 'w'}) -# -# get all grains of 'tgt' minions -# request('token', {'tgt': '*', 'fun': 'grains.items'}) -# -# execute some state and apply custom pillars to it -# request('token', {'tgt': '*', 'fun': 'state.sls', 'kwarg': { -# 'mods': 'name_of_state', 'pillar': {'some': 'pillar_data'} -# }}) diff --git a/salt_observer/forms.py b/salt_observer/forms.py index 0a467b170810b75188d89a855b33a439bbcc694e..73707e1904907ead7132b0438ffb0c5d37b0f781 100644 --- a/salt_observer/forms.py +++ b/salt_observer/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.utils.translation import ugettext, ugettext_lazy as _ @@ -74,6 +75,31 @@ class LoginForm(AuthenticationForm): username = forms.CharField(label='', max_length=255) password = forms.CharField(label='', widget=forms.PasswordInput) + def clean(self): + ''' + Overwriting this method to pass the current request down to the + backends via authenticate([...]) + ''' + username = self.cleaned_data.get('username') + password = self.cleaned_data.get('password') + + if username and password: + self.user_cache = authenticate( + username=username, + password=password, + request=self.request + ) + if self.user_cache is None: + raise forms.ValidationError( + self.error_messages['invalid_login'], + code='invalid_login', + params={'username': self.username_field.verbose_name}, + ) + else: + self.confirm_login_allowed(self.user_cache) + + return self.cleaned_data + class MinionEditForm(MarkdownFormMixin): class Meta: diff --git a/salt_observer/management/commands/__init__.py b/salt_observer/management/commands/__init__.py index c83ca2d11d058b1da850a02f704b9b51b67ca48e..7e5d299b2816f4876b830886f55ee0d1a93fa16e 100644 --- a/salt_observer/management/commands/__init__.py +++ b/salt_observer/management/commands/__init__.py @@ -1,4 +1,4 @@ -from salt_observer.cherry import SaltCherrypyApi +from salt_observer.saltapis import SaltCherrypy from getpass import getpass @@ -20,4 +20,4 @@ class ApiCommand(object): else: password = kwargs.get('password') - return SaltCherrypyApi(username, password) + return SaltCherrypy(username, password) diff --git a/salt_observer/private_settings.example.py b/salt_observer/private_settings.example.py index 2a4ab5d4af8bb7a2ae58118c382b025e337f9f0c..b1313ed93ecf438ed58b7f354bdb2e83b4206e46 100644 --- a/salt_observer/private_settings.example.py +++ b/salt_observer/private_settings.example.py @@ -26,9 +26,16 @@ DATABASES = { # Salt observer configuration SALT_API = { - 'host': 'localhost', - 'port': 8989, - 'protocol': 'http', + 'cherrypy': { + 'host': 'localhost', + 'port': 8989, + 'protocol': 'http', + }, + 'tornado': { + 'host': 'localhost', + 'port': 8002, + 'protocol': 'http' + } } # specify your salt network here to disable it in network visualization diff --git a/salt_observer/saltapis.py b/salt_observer/saltapis.py new file mode 100644 index 0000000000000000000000000000000000000000..befe3238280c2907330d2bb956544a5bbd183d6f --- /dev/null +++ b/salt_observer/saltapis.py @@ -0,0 +1,74 @@ +from django.conf import settings + +import requests +from getpass import getpass + + +class AbstractApi(object): + ''' + Defines an abstract api to inherit from. + You MUST specify a BASE_URL for your api to build a proper request url. + ''' + + BASE_URL = '' + + def __init__(self, username='', password='', token=''): + ''' Set a token to work with ''' + if not self.BASE_URL: + raise NotImplementedError('Please provide an BASE_URL') + + if token: + self.token = token + else: + self.token = self.obtain_auth_token(username, password) + + def request(self, method='get', resource='/', headers={}, data={}): + return getattr(requests, method)( + self.BASE_URL + resource, + headers=headers, + data=data + ) + + def obtain_auth_token(self, username, password): + res = self.request('post', '/login', headers={'Accept': 'application/json'}, data={ + 'username': username, + 'password': password, + 'eauth': 'pam' + }) + + if res.status_code != 200: + raise Exception('{} - {}'.format(res.status_code, res.text)) + + return res.json().get('return')[0].get('token') + + +class SaltCherrypy(AbstractApi): + + BASE_URL = '{protocol}://{host}:{port}'.format(**settings.SALT_API['cherrypy']) + + def logout(self): + return self.request('post', '/logout', headers={ + 'Accept': 'application/json', + 'X-Auth-Token': self.token + }) + + def get(self, module, target='*', api_args=[], api_kwargs={}): + return self.request( + 'post', + data={ + 'client': 'local', + 'fun': module, + 'tgt': target, + 'arg': api_args, + 'kwarg': api_kwargs + }, + headers={ + 'Accept': 'application/json', + 'X-Auth-Token': self.token + } + ).json().get('return')[0] + + +class SaltTornado(AbstractApi): + + BASE_URL = '{protocol}://{host}:{port}'.format(**settings.SALT_API['tornado']) diff --git a/salt_observer/views.py b/salt_observer/views.py index cfee1b25189d5de2d48b222994d511028e769622..9be9b9a339dc46db2aa17526c21707f6e6a89210 100644 --- a/salt_observer/views.py +++ b/salt_observer/views.py @@ -49,6 +49,12 @@ class Login(FormView): login(self.request, form.get_user()) return super().form_valid(form, *args, **kwargs) + def get_form_kwargs(self, *args, **kwargs): + '''dirty hack to pass the current request down to the backends''' + kwargs = super().get_form_kwargs(*args, **kwargs) + kwargs['request'] = self.request + return kwargs + def get_success_url(self): return self.request.GET.get('next', reverse_lazy('dashboard'))