Short description

I have several public wikis that are read-only for the general public, with any known user being able to edit pages. However, I don't want just anyone to register themselves, I want to only let my friends and friends of my friends be able to get access: you need to have someone who already has wiki access create a username for you.

The changes discussed below add an ACL line that lets you define who has permission to create new users on the wiki, and then also lets already-logged-in users create new users without logging out (since logged out users now can't create users at all). There are a couple of usability improvements to go along with it, and all of these changes have been tested and are in production right now.

FlorianFesti suggested the ACL path, and AlexanderSchremmer initially voiced concerns on IRC, but these were quickly assuaged.

Here are eight sets of changes that need to be made:

First, the default value goes in multiconfig.py, between acl_rights_after and acl_rights_valid:

acl_rights_createuser = u"All:write"

Second, add the following class to the end of wikiacl.py:

class CreateUserAccessControlList(AccessControlList):
    ''' Access Control List for Creating Users
    Control who may create new user accounts on the system.
    This has to be in your config, can't use #acl lines.
    Configuration options
       cfg.acl_rights_createuser
           Defines who is able to create users.  Permission is either
           "write" or nothing.
           Default: "All:write"
    '''
    def __init__(self, request):
        """Initialize an ACL, starting from <nothing>.
        """
        self.setLines(request.cfg)
    def setLines(self, cfg):
        self.clean()
        self.addCreateuser(cfg)
    def addCreateuser(self, cfg):
        self.addLine(cfg, cfg.acl_rights_createuser, remember=0)

Third, since we need ACLs on every major screen of the userform, let's import wikiacl at the top. Around line 10 of userform.py, change:

from MoinMoin import config, user, util, wikiutil

to:

from MoinMoin import config, user, util, wikiutil, wikiacl

All of the remaining changes are in userform.py.

Fourth, around line 144, right after the else:, add:

createuseracl = wikiacl.CreateUserAccessControlList(self.request).may(self.request, self.request.user.name, "write")

Fifth, around line 164 of userform.py, between the newuser = 1 and password blocks, replace the entire user.getUserId block with:

# create key is for existing users creating new users
# save key is for users creating or updating themselves
if form.has_key('save'):
    if user.getUserId(self.request, theuser.name):
        if theuser.name != self.request.user.name:
            return _("This user name already belongs to somebody else.")
        else:
            newuser = 0
# Now that we know if it's a new user or not, we can check the acl
if newuser and not createuseracl:
    return _("You are not allowed to create a user account.")
# create key is for existing users creating new users
# save key is for users creating or updating themselves
if newuser and form.has_key('create'):
    theuserisnew = self.request
    theuserisnew.saved_cookie = ''
    theuserisnew.auth_username = ''
    theuser = user.User(theuserisnew)
    theuser.name = form['username'][0]

Sixth, around line 279, after theuser.save(), we need to only reset the cookie if actually needed. Change the lines between theuser.save() and result = _("User preferences saved!") to:

# create key is for existing users creating new users
# save key is for users creating or updating themselves
if form.has_key('save'):
    self.request.user = theuser
    self.request.setCookie()

Seventh, we have to add the Create Profile button back into the form for users with ACL access. Around line 408, in asHTML() after self.make_form(), change the whole if self.request.user.valid: block to:

createuseracl = wikiacl.CreateUserAccessControlList(self.request).may(self.request, self.request.user.name, "write")
if self.request.user.valid:
    # User preferences interface
    buttons = [
        ('save', _('Save'))
    ]
    if createuseracl:
        buttons.append(('create', _('Create Profile')))
    buttons.append(('logout', _('Logout')))
else:
    # Login / register interface
    buttons = [
        # IMPORTANT: login should be first to be the default
        # button when a user click enter.
        ('login', _('Login')),
    ]
    if createuseracl:
        buttons.append(("save", _('Create Profile')))
    if self.cfg.mail_smarthost:
        buttons.append(("login_sendmail", _('Mail me my account data')))

This only adds the Create Profile button if the user has the right to create a new user. createuseracl will be true even if ACLs are disabled or if there is no custom new user creation ACL. You'll also notice that for logged in users, the Create Profile button has a new name, create. This will let us tell whether a logged in user is creating a new user, or updating their own user.

Eighth, the next change is purely cosmetic, but increases the usability of the page a bit. They remove the password verification box if the user isn't allowed to create a user or isn't already logged in (e.g. all they can do is log in). They also remove the email verification box if mailing isn't configured and the user isn't allowed to create a user or isn't already logged in, or adds a clarification quote in parethesis if they're logged out but mailing is configured.

Around line 444, replace the self.make_row(_('Password repeat'), [ and self.make_row(_('Email'), [ blocks with the following:

if self.request.user.valid or createuseracl:
    self.make_row(_('Password repeat'), [
        html.INPUT(
            type="password", size="36", name="password2",
        ),
        ' ', _('(Only when changing passwords)'),
    ])
if self.cfg.mail_smarthost and not createuseracl:
    self.make_row(_('Email'), [
        html.INPUT(
            type="text", size="36", name="email", value=self.request.user.email
        ),
        ' ', _('(Only for mailing your account data)', formatted=False),
    ])
elif createuseracl:
    self.make_row(_('Email'), [
        html.INPUT(
            type="text", size="36", name="email", value=self.request.user.email
        ),
        ' ',
    ])

That's it! You can now set a line in your wikiconfig.py like:

acl_rights_createuser = u"VitoMiliano:write All:"

to only let you register new users. Or you could do:

acl_rights_createuser = u"Known:write All:"

to let anyone who already has an account add a new one.

If you want all the userform.py changes, I've attached my version here (drop in replacement for 1.3.4's userform.py, but don't forget to make the changes to wikiacl.py and multiconfig.py!): userform.py

I would like to get a patch with all those changes, so I can test this on my test wiki. If you work with tla, getting this patch is very easy, just run tla changes --diffs > createuser.patch. If you don't work with tla I think that diff -ur original-moin-dir modified-moin-dir > createuser.patch will do the same. Both are much easier then to write manually the patch description on this page, and very easy to apply by other developers or users that want to try the patch. -- NirSoffer 2005-03-27 00:44:28

Alright! I believe I did it correctly: createuser.patch

The patch have some problems:

  1. Adding a "create account" button in the user preferences form is confusing and wrong. Create user account is either free, or an admin stuff, that only certain user can access. Its not related to the user preferences.
  2. There is no need to create a new type of AccessControlList to add new acl right.

  3. There is no need to patch userform.py - UserPreference is a macro - just provide you private wiki macro, and implement the ui as you like.

For a general solution, we can do this:

Add new 'register' right

Add new acl right to the valid rights, 'register' which is the right to register a user acount. The default install will give this right to the All group. It will look like this in multiconfig:

    acl_rights_before = u"All:+register"
    acl_rights_valid = ['read', 'write', 'revert', 'delete', 'register',
                        'admin',]

In a wiki that only known users are allowed to register accounts, this setting will be used in the configuration file:

    acl_rights_before = u"Known:+register"

In a wiki where only certain user in group RegisterGroup should register accounts:

    acl_rights_before = u"RegisterGroup:+register"

This change is trivial.

User interface change

Start redesign the login/register/user pref as described in UserPreferencesRedesign. This change is bigger but its a simpler user interface change.

Create new macro [[CreateAcount]], that will be in the new CreateAcount page that will show this:


name:

[                                  ]

password:

[                                  ]

repeat:

[                                  ]

This macro will validate the user using standard acl calls:

if not user.may.register(''):
    return _("You may not create user acounts in this wiki.")

This wil use the acl right defined in the config.

In userform.py, simplify the code and the login user interface to this:


name:

[                                  ]

password:

[                                  ]


The line about creating user account will show only if All have register right in the wiki of course, so a visitor will not be sent to a page just to tell "you are not allowed".

There is no need to enter an email address for the "Send Account Data", because in most cases the user also forgot what was the email address that he used when he registered. We should simply send the data to the email address in our data base.




        if (form.has_key('create') or
            form.has_key('create_only') or
            form.has_key('create_and_mail')):
            if not self.request.user.isSuperUser(): # added
                return _("User creation disabled in 'userform.py'.") # added

Is there a better way to do this?

...Ah... just discovered that a page (for instance "UserCreation") is also needed with content:

    [[UserPreferences(createonly)]]

MyTestPage


+1 --AlvaroTejeroMyTestPage


--FSB


I've been a moin user for a long time, and the issue of not being able to 'clamp down' a site when serving it publicly has always been a problem for me. In particular, I've noticed that people can use the site to spam other people, eg:

and hey presto: instant spamming! :( So, I'm very keen to get a solution to this issue.

One of the main problems seems to be the lack of separation of the 'User Preferences' page and the 'Create New User' page. Wouldn't it be simple to just:

  1. separate these pages (!)

    • has been done in 1.7
  2. allow people to restrict access to the 'Create New User' page (ie using the existing ACL techniques)

(This would have the nice side effect of getting rid of the confusing password-change vs password-enter behavior)

Another related note: it might be useful to me (and the general community) to have a nice overview write-up of the techniques used on the Moin site itself. I've noticed that you guys have done a great job of writing the code and using it yourselves to manage your codebase (eg the Bugs/Features)

Thanks for listening, Tushar. :)


This feature request is totally mangled and seems to consist of at least three different ones. In 1.7 it is possible to disable new user creation by disallowing the "newaccount" action. Please create a new one. -- JohannesBerg 2008-03-18 00:02:53


MoinMoin: FeatureRequests/NewUserCreationACL (last edited 2008-03-18 00:02:53 by JohannesBerg)