#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File: user.py
#
# Copyright 2018 Costas Tyfoxylos
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
"""
Main code for user.
.. _Google Python Style Guide:
http://google.github.io/styleguide/pyguide.html
"""
import logging
from towerlib.towerlibexceptions import InvalidValue, InvalidRole, InvalidOrganization
from .core import (Entity,
EntityManager,
validate_max_length,
validate_characters)
__author__ = '''Costas Tyfoxylos <ctyfoxylos@schubergphilis.com>'''
__docformat__ = '''google'''
__date__ = '''2018-01-03'''
__copyright__ = '''Copyright 2018, Costas Tyfoxylos'''
__credits__ = ["Costas Tyfoxylos"]
__license__ = '''MIT'''
__maintainer__ = '''Costas Tyfoxylos'''
__email__ = '''<ctyfoxylos@schubergphilis.com>'''
__status__ = '''Development''' # "Prototype", "Development", "Production".
# This is the main prefix used for logging
LOGGER_BASENAME = '''user'''
LOGGER = logging.getLogger(LOGGER_BASENAME)
LOGGER.addHandler(logging.NullHandler())
[docs]class User(Entity):
"""Models the user entity of ansible tower."""
def __init__(self, tower_instance, data):
Entity.__init__(self, tower_instance, data)
@property
def username(self):
"""The username of the user.
Returns:
string: The username of the user.
"""
return self._data.get('username')
@username.setter
def username(self, value):
"""Update the username of the user.
Returns:
None:
"""
max_characters = 150
valid_metacharacters = '@.+-_'
conditions = [validate_max_length(value, max_characters),
validate_characters(value, extra_chars=valid_metacharacters)]
if all(conditions):
self._update_values('username', value)
else:
raise InvalidValue(f'{value} is invalid. Condition max_characters must be less or equal to '
f'{max_characters} and valid character are only alphanums and {valid_metacharacters}')
@property
def password(self):
"""The password of the user.
Returns:
string: The masked password value of the user.
"""
return '*********'
@password.setter
def password(self, value):
"""Update the password of the user.
Returns:
None:
"""
self._update_values('password', value)
@property
def first_name(self):
"""The first name of the user.
Returns:
string: The first name of the user.
"""
return self._data.get('first_name')
@first_name.setter
def first_name(self, value):
"""Update the first name of the user.
Returns:
None:
"""
max_characters = 30
conditions = [validate_max_length(value, max_characters)]
if all(conditions):
self._update_values('first_name', value)
else:
raise InvalidValue(f'{value} is invalid. Condition max_characters must be less or equal to '
f'{max_characters}')
@property
def last_name(self):
"""The last name of the user.
Returns:
string: The last name of the user.
"""
return self._data.get('last_name')
@last_name.setter
def last_name(self, value):
"""Update the last name of the user.
Returns:
None:
"""
max_characters = 30
conditions = [validate_max_length(value, max_characters)]
if all(conditions):
self._update_values('last_name', value)
else:
raise InvalidValue(value)
@property
def email(self):
"""The email of the user.
Returns:
string: The email of the user.
"""
return self._data.get('email')
@email.setter
def email(self, value):
"""Update the email address of the user.
Returns:
None:
"""
max_characters = 254
conditions = [validate_max_length(value, max_characters)]
if all(conditions):
self._update_values('email', value)
else:
raise InvalidValue(f'{value} is invalid. Condition max_characters must be less or equal to '
f'{max_characters}')
@property
def is_superuser(self):
"""The superuser status of the user.
Returns:
bool: True if the user is a superuser, False otherwise.
"""
return self._data.get('is_superuser')
@is_superuser.setter
def is_superuser(self, value):
"""Update the is_superuser field of the user.
Returns:
None:
"""
self._update_values('is_superuser', value)
@property
def is_system_auditor(self):
"""The system auditor status of the user.
Returns:
bool: True if the user is a system auditor, False otherwise.
"""
return self._data.get('is_system_auditor')
@is_system_auditor.setter
def is_system_auditor(self, value):
"""Update the is_system_auditor field of the user.
Returns:
None:
"""
self._update_values('is_system_auditor', value)
@property
def ldap_dn(self):
"""The ldap dn setting for the user.
Returns:
string: The ldap dn entry for the user.
"""
return self._data.get('ldap_dn')
@property
def external_account(self):
"""The external account entry for the user.
Returns:
string: The external account entry for the user if it exists.
None: If no entry exists.
"""
return self._data.get('external_account')
@property
def auth(self):
"""The authentication setting for the user.
Returns:
list: Used authentication methods set for the user.
"""
return self._data.get('auth')
@property
def organizations(self):
"""The organizations that the user is part of.
Returns:
EntityManager: EntityManager of the organizations.
"""
url = self._data.get('related', {}).get('organizations')
return EntityManager(self._tower,
entity_object='Organization',
primary_match_field='name',
url=url)
@property
def roles(self):
"""The roles that the user has.
Returns:
EntityManager: EntityManager of the roles.
"""
url = self._data.get('related', {}).get('roles')
return EntityManager(self._tower,
entity_object='Role',
primary_match_field='name',
url=url)
@property
def teams(self):
"""The teams that the user is part of.
Returns:
EntityManager: EntityManager of the teams.
"""
url = self._data.get('related', {}).get('teams')
return EntityManager(self._tower,
entity_object='Team',
primary_match_field='name',
url=url)
@property
def projects(self):
"""The projects that the user is part of.
Returns:
EntityManager: EntityManager of the projects.
"""
url = self._data.get('related', {}).get('projects')
return EntityManager(self._tower,
entity_object='Project',
primary_match_field='name',
url=url)
@property
def credentials(self):
"""The credentials that the user has.
Returns:
EntityManager: EntityManager of the credentials.
"""
url = self._data.get('related', {}).get('credentials')
return EntityManager(self._tower,
entity_object='Credential',
primary_match_field='name',
url=url)
@property
def last_login(self):
"""The last time the user logged in to the system.
Returns:
datetime: The datetime object of the date and time of the last login for the user.
None: If there is no entry for the last login date.
"""
return self._to_datetime(self._data.get('last_login'))
def _assign_permission_role(self, role_id, disassociate=False):
payload = {'id': role_id}
if disassociate:
payload['disassociate'] = True
url = f"{self._tower.host}{self._data.get('related', {}).get('roles')}"
response = self._tower.session.post(url, json=payload)
if not response.ok:
self._logger.error('Error editing the role permissions for user "%s", response was :"%s"', self.username,
response.text)
return response.ok
[docs] def associate_with_organization_role(self, organization, role):
"""Associate a user to an organizational role.
Args:
organization: The organization that we want to assign to
role: The role we want to assign to the object
Returns:
bool: If it managed to associate the user to the organization
"""
organization_ = self._tower.get_organization_by_name(organization)
if not organization_:
raise InvalidOrganization(organization)
role_id = organization_._get_object_role_id(role) # pylint: disable=protected-access
if role_id is None:
raise InvalidRole(role)
return self._assign_permission_role(role_id)
[docs] def disassociate_from_organization_role(self, organization, role):
"""Disassociate a user to an organizational role.
Args:
organization: The organization object that we want to assign to
role: The role we want to assign to the object
Returns:
bool: If it managed to disassociate the user to the organization
"""
organization_ = self._tower.get_organization_by_name(organization)
if not organization_:
raise InvalidOrganization(organization)
role_id = organization_._get_object_role_id(role) # pylint: disable=protected-access
if role_id is None:
raise InvalidRole(role)
return self._assign_permission_role(role_id, disassociate=True)