Skip to content

Commit

Permalink
Limit password resets to self-service users
Browse files Browse the repository at this point in the history
The portal was able to reset all passwords except for admin users.
Password resets and future self-service features must be limited to
self-service users.

The patch drops the 'System: Change User password' permission and
replaces it with two additional permissions for users and stage users.
It also introduces a new group for self-service capable users and an
automember rule. When a self-registered user is approved by an admin, it
is automatically added to the self-service group.

The patch also renames the portal user, role and privilege to be more
consistent with 'self-service' naming convention.

Closes #36
  • Loading branch information
tiran committed Aug 26, 2015
1 parent 211d420 commit 3e51cce
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 9 deletions.
2 changes: 2 additions & 0 deletions freeipa_community_portal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class Config(object):

captcha_length = 4
umask = 0o027
# marker for self-service capable users
userclass = u'self-service capable'

metadata = MetaData()

Expand Down
4 changes: 3 additions & 1 deletion freeipa_community_portal/model/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ipalib import api, errors

from . import api_connect
from ..config import config


class User(object): # pylint: disable=too-few-public-methods
Expand Down Expand Up @@ -69,5 +70,6 @@ def _call_api(self):
givenname=self.given_name,
sn=self.family_name,
uid=self.username,
mail=self.email
mail=self.email,
userclass=config.userclass,
)
162 changes: 154 additions & 8 deletions install/create-portal-user
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,43 @@ import subprocess

from ipalib import api
from ipalib import errors
from ipapython.dn import DN


logger = logging.getLogger('create-portal-user')

PRIVILEGE = u'Portal management privilege'
PRIVILEGE_DESCRIPTION = u'Portal privileges'
SELFSERVICE_GROUP = u'self-service'
SELFSERVICE_GROUP_DESCRIPTION = u"""Self-service users
Members of the group are able to use the self-service portal for some tasks,
e.g. request a password reset."""

SELFSERVICE_USER_PERMISSION = u'Self-Service Change User Password'
SELFSERVICE_STAGEUSER_PERMISSION = u'Self-Service Change Stage User Password'

SELFSERVICE_USERCLASS = u'self-service capable'
SELFSERVICE_AUTOMEMBER_DESCRIPTION = (
u"Automember rule for self-service portals to add approved "
u"self-registered users to self-service group."
)

PRIVILEGE = u'Self-Service Management Privilege'
PRIVILEGE_DESCRIPTION = u"Self-service portal privilege for e.g. password resets"
PRIVILEGE_PERMISSIONS = [
SELFSERVICE_USER_PERMISSION,
SELFSERVICE_STAGEUSER_PERMISSION,
u'System: Add Stage User',
u'System: Read Stage User',
u'System: Change User password',
u'System: Read User Standard Attributes',
u'System: Read User Addressbook Attributes',
]
ROLE = u'Portal management'
ROLE_DESCRIPTION = u'self-service portals'
USER = u'portal'

ROLE = u'Self-Service Portal Management'
ROLE_DESCRIPTION = u'Self-service portals'

USER = u'self-service-portal'
USER_FIRST = u'Self-Service'
USER_LAST = u'Portal'

KEYTAB_OWNER = 'apache'
KEYTAB = '/etc/ipa/portal.keytab'

Expand All @@ -55,6 +76,20 @@ def tounicode(s):
parser = argparse.ArgumentParser(
description='Create user for community portal'
)
parser.add_argument(
'--selfservice-group',
dest='selfservice_group',
help="Group for self-service users (default: '%s')" % SELFSERVICE_GROUP,
default=SELFSERVICE_GROUP,
type=tounicode
)
parser.add_argument(
'--userclass',
dest='automember_userclass',
help="User class value for automember rule (default: '%s')" % SELFSERVICE_USERCLASS,
default=SELFSERVICE_USERCLASS,
type=tounicode
)
parser.add_argument(
'--privilege',
dest='privilege',
Expand Down Expand Up @@ -113,6 +148,59 @@ def api_connect():
api.Command.ping()


def create_selfservice_group(name):
try:
api.Command.group_add(
name,
description=SELFSERVICE_GROUP_DESCRIPTION)
except errors.DuplicateEntry:
logger.warn("Group '%s' already exists.", name)
else:
logger.info("Created group '%s'", name)


def create_selfservice_permission(name, container, memberof):
admins = DN(('cn', u'admins'), api.env.container_group, api.env.basedn)
permlocation = DN(container, api.env.basedn)
options = {
'ipapermbindruletype': u'permission',
'ipapermright': [u'write'],
'memberof': [memberof],
'ipapermlocation': permlocation,
'extratargetfilter': [
# Explicitly forbid password resets for admins.
u'(!(memberOf=%s))' % admins,
u'(objectclass=posixaccount)'
]
}
try:
api.Command.permission_add(name, **options)
except errors.DuplicateEntry:
logger.warn("Permission '%s' already exists.", name)
else:
logger.info("Created permission '%s' in '%s'", name, permlocation)


def create_automember_rule(groupname, userclass):
try:
api.Command.automember_add(
groupname,
type=u'group',
description=SELFSERVICE_AUTOMEMBER_DESCRIPTION)
except errors.DuplicateEntry:
logger.warn("Automember rule for group '%s' already exists.",
groupname)
else:
logger.info("Created automember rule for '%s'", groupname)
api.Command.automember_add_condition(
groupname,
type=u'group',
key=u'userclass',
automemberinclusiveregex=userclass,
)
logger.info(" Added rule userclass='%s'", userclass)


def create_privilege(name, permissions):
try:
api.Command.privilege_add(
Expand Down Expand Up @@ -151,7 +239,10 @@ def create_role(name, privilege):

def create_user(name):
try:
api.Command.user_add(name, givenname=u'Self', sn=u'Service')
api.Command.user_add(
name,
givenname=USER_FIRST,
sn=USER_LAST)
except errors.DuplicateEntry:
logger.warn("User '%s' already exists", name)
return False
Expand All @@ -170,7 +261,17 @@ def create_keytab(username, keytab, owner):
logger.warn("Keytab '%s' already exists.", keytab)
logger.info("Skipping ipa-getkeytab")
return False
server = api.env.server

directory = os.path.dirname(keytab)
if not os.path.isdir(directory):
os.makedirs(directory, mode=0o755)

try:
server = api.env.server
except AttributeError:
# We are running on the same host as the IPA service.
server = api.env.host

result = api.Command.user_show(username, all=True)
result = result[u'result']
principal = result[u'krbprincipalname'][0]
Expand All @@ -183,12 +284,56 @@ def create_keytab(username, keytab, owner):
return True


def _cleanup(args):
"""Internal testing helper
"""
commands = [
('user_del', args.username, {}),
('role_del', args.role, {}),
('privilege_del', args.privilege, {}),
('automember_del', args.selfservice_group, {'type': u'group'}),
('permission_del', SELFSERVICE_USER_PERMISSION, {}),
('permission_del', SELFSERVICE_STAGEUSER_PERMISSION, {}),
('group_del', args.selfservice_group, {}),
]
for command, arg, kwargs in commands:
try:
getattr(api.Command, command)(arg, **kwargs)
except errors.NotFound:
logger.info("- %s('%s')", command, arg)
else:
logger.info("+ %s('%s')", command, arg)

try:
os.unlink(args.keytab)
except OSError as e:
logger.error(e)


def main():
args = parser.parse_args()
try:
api_connect()
except errors.PublicError as e:
parser.exit(2, "ERROR: FreeIPA is not responding:\n %s\n" % e)

# _cleanup(args)

create_selfservice_group(args.selfservice_group)
create_selfservice_permission(
SELFSERVICE_USER_PERMISSION,
api.env.container_user,
args.selfservice_group,
)
create_selfservice_permission(
SELFSERVICE_STAGEUSER_PERMISSION,
api.env.container_stageuser,
args.selfservice_group,
)
create_automember_rule(
args.selfservice_group,
args.automember_userclass
)
create_privilege(args.privilege, PRIVILEGE_PERMISSIONS)
create_role(args.role, args.privilege)
create_user(args.username)
Expand All @@ -204,4 +349,5 @@ if __name__ == '__main__':
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

main()

0 comments on commit 3e51cce

Please sign in to comment.