Commit 08dbcb7f authored by Shane Hathaway's avatar Shane Hathaway

Merged UserBootstrappingBranch and added documentation to CHANGES, FAQ,

and SECURITY.
parent cf9fdb30
......@@ -33,7 +33,16 @@ Zope changes
- Improved Ownership controls. Now you simply choose whether
or not to take ownership of sub-objects when taking
ownership. There is no need to control implicit/explicit
ownership.
ownership.
- Changed the Zope installation procedure so it is only
necessary to create one user account and that user is
stored in the ZODB.
- Implemented the "emergency user" concept, which is the new
name for what was called the superuser.
Bugs Fixed
......@@ -58,3 +67,10 @@ Zope changes
- Undid a bug fix that caused the DateTime unit tests to fail.
- Removed the requirement that an "access" file exist.
"access" is now only needed to create an emergency user
account.
- Disabled the monitor port by default because, initially,
there is no emergency user, and thus no password that
can be used to protect the port.
......@@ -110,6 +110,28 @@ Zope Installation Frequently Asked Questions
be added to the environment of the process when it is started
by PCGI.
6. I have forgotten the only password used to access the site or
I have modified the security settings in such a way that even
I can't get access. How do I fix it?
Assuming you have write access to the directory where Zope is
installed, you can create a temporary "emergency user" using
the 'zpasswd.py' script::
python zpasswd.py access
Follow the prompts to enter a user name and password.
'zpasswd.py' will write the file named 'access'. Zope will
look for this file the next time it starts. After restarting,
browse to Zope's management interface and use the name and
password you entered.
As the emergency user, you are allowed to create user folders
and user accounts as well as adjust security settings but you
are not allowed to create objects like DTML methods or folders.
This is a safety precaution.
See more details in the file SECURITY.TXT.
Managing the Zope process
......
Setting the Zope "super manager" name and password
Setting the initial user name and password
Because Zope is managed through the web, user names and passwords must be
used to assure that only authorized people can make changes to a Zope
installation. User names and passwords are normally defined by creating
and modifying user folders within Zope.
A special "super manager" user name and password are defined outside
the application for two reasons
* Some user name and password is needed to "bootstrap" creation of
normal managers of your Zope site.
* The "super manager" provides an all-powerful user that can do
(almost) anything in the application, and whose password cannot
be changed through the application user interface.
This user name and password is defined in the 'access' file located
in the Zope directory. It should be readable only by the user
installation.
Some user name and password is needed to "bootstrap" the creation of
normal managers of your Zope site. This is accomplished through the
use of the file 'inituser'. The first time Zope starts, it will detect
that no users have been defined in the root user folder. It will search
for the 'inituser' file and, if it exists, will add the user defined
in the file to the root user folder.
Normally, 'inituser' is created by the Zope install scripts. Either
the installer prompts for the password or a randomly generated
password is created and displayed at the end of the build script.
You can use the 'zpasswd.py' script to create 'inituser' yourself.
Execute 'zpasswd.py' like this::
python zpasswd.py inituser
The script will prompt you for the name, password, and allowed
domains. The default is to encode the password with SHA, so please
remember this password as there is no way to recover it (although
'zpasswd.py' lets you reset it.)
In some situations you may need to bypass normal security controls
because you have lost your password or because the security settings
have been mixed up. Zope provides a facility called an "emergency
user" so that you can reset passwords and correct security
settings.
The emergency user password must be defined outside the application
user interface. It is defined in the 'access' file located
in the Zope directory. It should be readable only by the user
as which your web server runs.
The super manager username and password should only be used when
defining the normal management users and passwords and when dealing
with unusual situations, like lost (or hacked) manager user names and
passwords.
To create the emergency user, use 'zpasswd.py' to create the
'access' file like this::
python zpasswd.py access
In order to provide a somewhat higher level of security, various
encoding schemes are supported which provide access to either SHA-1
encryption or the standard UNIX crypt facility if it's been compiled
encryption or the standard UNIX crypt facility if it has been compiled
into Python. Unless you have some special requirements (see below),
you should use the SHA-1 facility, which is the default.
The access file should consist of a single line of the form:
Format of 'inituser' and 'access'
name:password
A password file should consist of a single line of the form:
The build scripts automatically create an 'access' file for you,
using a default username and a randomly generated password which
will be given at the end of the build script. The default is to
encode this with SHA, so please remember this password as there is no
way to recover it.
name:password
Note that you may also add an optional third component to the line
in the access file to restrict super manager access by domain.
in the access file to restrict access by domain.
For example, the line:
mario:nintendoRules:*.mydomain.com
in your 'access' file will only allow super manager access to your
installation from *.mydomain.com machines. Attempts to access the
system from other domains will fail, even if the correct superuser
name and password are used.
in your 'access' file will only allow permit emergency user access
from *.mydomain.com machines. Attempts to access the system from
other domains will fail, even if the correct emergency user name
and password are used.
Note that there is now a program to change the password,
'zpasswd.py', which if run will explain how to use it, and if run it
its most basic form will prompt for all information.
Please note that if you use the ZServer monitor capability, you will
need to run with a clear text password in this beta release.
Please note that if you use the ZServer monitor capability, you will
need to run with a clear text password.
Setting permissions on the var directory.
......@@ -73,3 +82,4 @@ Setting permissions on the var directory.
If you change the way you run Zope you may need to modify the permissions
of the var directory and the files in it to allow Zope to read and write
under its changed userid.
......@@ -143,7 +143,7 @@ def main(args):
import compilezpy
print '-'*78
import zpasswd; zpasswd.write_access(home, user, group)
import zpasswd; zpasswd.write_inituser(home, user, group)
import default_content; default_content.main(home, user, group)
import make_resource; make_resource.main(home, pcgi, user, group)
import make_start; make_start.sh(home, user, group)
......
......@@ -85,8 +85,8 @@
__doc__='''Support for owned objects
$Id: Owned.py,v 1.7 2000/11/09 14:14:09 chrism Exp $'''
__version__='$Revision: 1.7 $'[11:-2]
$Id: Owned.py,v 1.8 2000/12/05 18:49:42 shane Exp $'''
__version__='$Revision: 1.8 $'[11:-2]
import Globals, urlparse, SpecialUsers, ExtensionClass, string
from AccessControl import getSecurityManager
......@@ -267,14 +267,14 @@ class Owned(ExtensionClass.Base):
else:
# Otherwise change the ownership
user=getSecurityManager().getUser()
if aq_base(user) is SpecialUsers.super:
__creatable_by_super__=getattr(self,
'__creatable_by_super__',
None)
if (__creatable_by_super__ is None or
(not __creatable_by_super__())):
raise SuperCannotOwn, (
"Objects cannot be owned by the superuser")
if (SpecialUsers.emergency_user and
aq_base(user) is SpecialUsers.emergency_user):
__creatable_by_emergency_user__=getattr(
self,'__creatable_by_emergency_user__', None)
if (__creatable_by_emergency_user__ is None or
(not __creatable_by_emergency_user__())):
raise EmergencyUserCannotOwn, (
"Objects cannot be owned by the emergency user")
self.changeOwnership(user)
# Force all subs to acquire ownership!
......@@ -288,8 +288,8 @@ class Owned(ExtensionClass.Base):
Globals.default__class_init__(Owned)
class SuperCannotOwn(Exception):
"The superuser cannot own anything"
class EmergencyUserCannotOwn(Exception):
"The emergency user cannot own anything"
class EditUnowned(Exception):
"Can't edit unowned executables"
......
......@@ -85,8 +85,8 @@
__doc__='''Objects that implement Permission-based roles.
$Id: PermissionRole.py,v 1.8 2000/11/20 10:51:21 jim Exp $'''
__version__='$Revision: 1.8 $'[11:-2]
$Id: PermissionRole.py,v 1.9 2000/12/05 18:49:42 shane Exp $'''
__version__='$Revision: 1.9 $'[11:-2]
import sys
......@@ -134,6 +134,7 @@ class PermissionRole(Base):
return r
# This is used when a permission maps explicitly to no permission.
_what_not_even_god_should_do=[]
class imPermissionRole(Base):
......
......@@ -84,18 +84,18 @@
##############################################################################
"""Access control package"""
__version__='$Revision: 1.115 $'[11:-2]
__version__='$Revision: 1.116 $'[11:-2]
import Globals, socket, regex, SpecialUsers
import os
from Globals import HTMLFile, MessageDialog, Persistent, PersistentMapping
from string import join,strip,split,lower
from string import join, strip, split, lower, upper
from App.Management import Navigation, Tabs
from Acquisition import Implicit
from OFS.SimpleItem import Item
from base64 import decodestring
from App.ImageFile import ImageFile
from Role import RoleManager
from string import split, join, upper
from PermissionRole import _what_not_even_god_should_do, rolesForPermissionOn
from AuthEncoding import pw_validate
......@@ -187,7 +187,7 @@ class BasicUser(Implicit):
def authenticate(self, password, request):
passwrd=self._getPassword()
result = pw_validate(passwrd, password)
result = pw_validate(passwrd, password)
domains=self.getDomains()
if domains:
return result and domainSpecMatch(domains, request)
......@@ -300,14 +300,15 @@ class SimpleUser(BasicUser):
return tuple(self.domains)
class SpecialUser(SimpleUser):
"""Class for special users, like super and nobody"""
"""Class for special users, like emergency user and nobody"""
def getId(self): pass
class User(SimpleUser, Persistent):
"""Standard User object"""
class Super(SpecialUser):
"""Super user
class UnrestrictedUser(SpecialUser):
"""User that passes all security checks. Note, however, that modules
like Owner.py can still impose restrictions.
"""
def allowed(self,parent,roles=None):
return roles is not _what_not_even_god_should_do
......@@ -318,32 +319,52 @@ class Super(SpecialUser):
def has_permission(self, permission, object): return 1
def readUserAccessFile(filename):
'''Reads an access file from INSTANCE_HOME.
Returns name, password, domains, remote_user_mode.
'''
try:
f = open(os.path.join(INSTANCE_HOME, filename), 'r')
line = f.readline()
f.close()
except IOError:
return None
if line:
data = split(strip(line), ':')
remote_user_mode = not data[1]
try: ds = split(data[2], ' ')
except: ds = []
return data[0], data[1], ds, remote_user_mode
else:
return None
# Create emergency user.
_remote_user_mode=0
try:
f=open('%s/access' % INSTANCE_HOME, 'r')
except IOError:
raise 'InstallError', (
'No access file found at %s - see INSTALL.txt' % INSTANCE_HOME
)
try:
data=split(strip(f.readline()),':')
f.close()
_remote_user_mode=not data[1]
try: ds=split(data[2], ' ')
except: ds=[]
super=Super(data[0],data[1],('manage',), ds)
del data
except:
raise 'InstallError', 'Invalid format for access file - see INSTALL.txt'
info = readUserAccessFile('access')
if info:
_remote_user_mode = info[3]
emergency_user = UnrestrictedUser(
info[0], info[1], ('manage',), info[2])
else:
emergency_user = None
super = emergency_user # Note: use of the 'super' name is deprecated.
del info
nobody=SpecialUser('Anonymous User','',('Anonymous',), [])
system=Super('System Processes','',('manage',), [])
system=UnrestrictedUser('System Processes','',('manage',), [])
# stuff these in a handier place for importing
SpecialUsers.nobody=nobody
SpecialUsers.system=system
SpecialUsers.super=super
SpecialUsers.emergency_user=emergency_user
# Note: use of the 'super' name is deprecated.
SpecialUsers.super=emergency_user
class BasicUserFolder(Implicit, Persistent, Navigation, Tabs, RoleManager,
......@@ -417,9 +438,11 @@ class BasicUserFolder(Implicit, Persistent, Navigation, Tabs, RoleManager,
_remote_user_mode=_remote_user_mode
_super=super
_emergency_user=emergency_user
# Note: use of the '_super' name is deprecated.
_super=emergency_user
_nobody=nobody
def validate(self,request,auth='',roles=None):
if roles is _what_not_even_god_should_do:
......@@ -452,11 +475,12 @@ class BasicUserFolder(Implicit, Persistent, Navigation, Tabs, RoleManager,
return None
name,password=tuple(split(decodestring(split(auth)[-1]), ':', 1))
# Check for superuser
super=self._super
if self._isTop() and (name==super.getUserName()) and \
super.authenticate(password, request):
return super
# Check for emergency user
emergency_user=self._emergency_user
if (emergency_user and self._isTop() and (
name == emergency_user.getUserName())
and emergency_user.authenticate(password, request)):
return emergency_user
# Try to get user
user=self.getUser(name)
......@@ -520,10 +544,11 @@ class BasicUserFolder(Implicit, Persistent, Navigation, Tabs, RoleManager,
return ob
return None
# Check for superuser
super=self._super
if self._isTop() and (name==super.getUserName()):
return super
# Check for emergency user
emergency_user=self._emergency_user
if (emergency_user and self._isTop() and
name==emergency_user.getUserName()):
return emergency_user
# Try to get user
user=self.getUser(name)
......@@ -572,7 +597,8 @@ class BasicUserFolder(Implicit, Persistent, Navigation, Tabs, RoleManager,
title ='Illegal value',
message='Password and confirmation must be specified',
action ='manage_main')
if self.getUser(name) or (name==self._super.getUserName()):
if self.getUser(name) or (self._emergency_user and
name == self._emergency_user.getUserName()):
return MessageDialog(
title ='Illegal value',
message='A user with the specified name already exists',
......@@ -690,7 +716,7 @@ class BasicUserFolder(Implicit, Persistent, Navigation, Tabs, RoleManager,
if hasattr(self, 'aq_base'): self=self.aq_base
container.__allow_groups__=self
def __creatable_by_super__(self): return 1
def __creatable_by_emergency_user__(self): return 1
def _setId(self, id):
if id != self.id:
......@@ -759,6 +785,22 @@ class UserFolder(BasicUserFolder):
for name in names:
del self.data[name]
def _createInitialUser(self):
"""
If there are no users in this user folder,
populates from the 'inituser' file in INSTANCE_HOME.
Called only by OFS.Application.initialize().
"""
if len(self.data) < 1:
info = readUserAccessFile('inituser')
if info:
name, password, domains, remote_user_mode = info
self._doAddUser(name, password, ('Manager',), domains)
try:
os.remove(os.path.join(INSTANCE_HOME, 'inituser'))
except:
pass
Globals.default__class_init__(UserFolder)
......
......@@ -85,8 +85,8 @@
__doc__='''Application support
$Id: Application.py,v 1.133 2000/09/07 16:07:20 brian Exp $'''
__version__='$Revision: 1.133 $'[11:-2]
$Id: Application.py,v 1.134 2000/12/05 18:49:43 shane Exp $'''
__version__='$Revision: 1.134 $'[11:-2]
import Globals,Folder,os,sys,App.Product, App.ProductRegistry, misc_
import time, traceback, os, string, Products
......@@ -186,7 +186,7 @@ class Application(Globals.ApplicationDefaultPermissions,
'Control_Panel')
# This class-default __allow_groups__ ensures that the
# superuser can still access the system if the top-level
# emergency user can still access the system if the top-level
# UserFolder is deleted. This is necessary to allow people
# to replace the top-level UserFolder object.
......@@ -362,6 +362,14 @@ def initialize(app):
get_transaction().note('Added Globals')
get_transaction().commit()
# Install the initial user.
if hasattr(app, 'acl_users'):
users = app.acl_users
if hasattr(users, '_createInitialUser'):
app.acl_users._createInitialUser()
get_transaction().note('Created initial user')
get_transaction().commit()
install_products(app)
def import_products(_st=type('')):
......
......@@ -104,7 +104,7 @@ def main(me):
import build_extensions
user=group=''
import default_content; default_content.main(home, user, group)
import zpasswd; zpasswd.write_access(home, user, group)
import zpasswd; zpasswd.write_inituser(home, user, group)
pcgi=os.path.join(home, 'Zope.cgi')
import make_start; make_start.sh(home, user, group)
......
......@@ -204,8 +204,8 @@ Options:
allows interactive Python style access to a running ZServer. To
access the server see medusa/monitor_client.py or
medusa/monitor_client_win32.py. The monitor server password is the
same as the Zope super manager password set in the 'access'
file. The default is %(MONITOR_PORT)s.
same as the Zope emergency user password set in the 'access'
file. The default is to not start up a monitor server.
The port can be preeceeded by an ip address follwed by a colon
to specify an address to listen on. This allows different servers
......@@ -337,7 +337,7 @@ FTP_PORT=8021
PCGI_FILE='Zope.cgi'
## Monitor configuration
MONITOR_PORT=8099
MONITOR_PORT=0
# Module to be published, which must be Main or Zope
MODULE='Zope'
......@@ -444,7 +444,6 @@ try:
elif o=='-f':
FTP_PORT=server_info(FTP_PORT, v)
elif o=='-P':
MONITOR_PORT=server_info(MONITOR_PORT, v, 99)
HTTP_PORT=server_info(HTTP_PORT, v, 80)
FTP_PORT=server_info(FTP_PORT, v, 21)
......@@ -663,14 +662,21 @@ if FCGI_PORT and not READ_ONLY:
# Monitor Server
if MONITOR_PORT:
if type(MONITOR_PORT) is type(0):
MONITOR_PORT=((IP_ADDRESS, MONITOR_PORT),)
for address, port in MONITOR_PORT:
from AccessControl.User import super
monitor=secure_monitor_server(
password=super._getPassword(),
hostname=address,
port=port)
from AccessControl.User import emergency_user
if emergency_user:
pw = emergency_user._getPassword()
else:
pw = None
zLOG.LOG("z2", zLOG.WARNING, 'Monitor server not started'
' because no emergency user exists.')
if pw:
if type(MONITOR_PORT) is type(0):
MONITOR_PORT=((IP_ADDRESS, MONITOR_PORT),)
for address, port in MONITOR_PORT:
monitor=secure_monitor_server(
password=pw,
hostname=address,
port=port)
# Try to set uid to "-u" -provided uid.
# Try to set gid to "-u" user's primary group.
......@@ -718,11 +724,3 @@ if not READ_ONLY:
sys.ZServerExitCode=0
asyncore.loop()
sys.exit(sys.ZServerExitCode)
......@@ -83,9 +83,9 @@
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""Zope password change system"""
"""Zope user bootstrap system"""
__version__='$Revision: 1.10 $ '[11:-2]
__version__='$Revision: 1.11 $ '[11:-2]
import sys, string, sha, binascii, whrandom, getopt, getpass, os
......@@ -112,29 +112,31 @@ def generate_passwd(password, encoding):
return pw
def write_access(home, user='', group=''):
def write_generated_password(home, ac_path, username):
import whrandom
pw_choices = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"abcdefghijklmnopqrstuvwxyz" \
"0123456789!"
acfile=open(ac_path, 'w')
pw = ''
for i in range(8):
pw = pw + whrandom.choice(pw_choices)
acfile.write('%s:%s' % (username, generate_passwd(pw, 'SHA')))
acfile.close()
os.system('chmod 644 %s' % ac_path)
return pw
def write_access(home, user='', group=''):
ac_path=os.path.join(home, 'access')
if not os.path.exists(ac_path):
print '-'*78
print 'creating default access file'
acfile=open(ac_path, 'w')
pw = ''
for i in range(8):
pw = pw + whrandom.choice(pw_choices)
acfile.write('superuser:' + generate_passwd(pw, 'SHA'))
acfile.close()
os.system('chmod 644 access')
pw = write_generated_password(home, ac_path, 'emergency')
print """Note:
The super user name and password are 'superuser'
The emergency user name and password are 'emergency'
and '%s'.
You can change the superuser name and password with the
You can change the emergency name and password with the
zpasswd script. To find out more, type:
%s zpasswd.py
......@@ -142,6 +144,23 @@ def write_access(home, user='', group=''):
import do; do.ch(ac_path, user, group)
def write_inituser(home, user='', group=''):
ac_path=os.path.join(home, 'inituser')
if not os.path.exists(ac_path):
print '-'*78
print 'creating default inituser file'
pw = write_generated_password(home, ac_path, 'admin')
print """Note:
The initial user name and password are 'admin'
and '%s'.
You can change the name and password through the web
interface or using the 'zpasswd.py' script.
""" % pw
import do; do.ch(ac_path, user, group)
def main(argv):
short_options = ':u:p:e:d:'
long_options = ['username=',
......@@ -149,13 +168,12 @@ def main(argv):
'encoding=',
'domains=']
usage = """%s [options] filename
usage = """Usage: %s [options] filename
If this program is called without command-line options, it will prompt
for all necessary information. The available options are:
-u / --username=
Set the username to be used for the superuser
Set the username to be used for the initial user or the emergency user
-p / --password=
Set the password
......@@ -167,10 +185,10 @@ for all necessary information. The available options are:
Set the domain names that the user user can log in from. Defaults to
any. OPTIONAL.
Filename is required, and should be the name of the file to store the
information in (usually "access").
Filename is required and should be the name of the file to store the
information in (usually "inituser" or "access").
Copyright (C) 1999 Digital Creations, Inc.
Copyright (C) 1999, 2000 Digital Creations, Inc.
""" % argv[0]
try:
......@@ -186,7 +204,7 @@ Copyright (C) 1999 Digital Creations, Inc.
if len(optlist) > 0:
# Set the sane defaults
username = 'superuser'
username = ''
encoding = 'SHA'
domains = ''
......@@ -240,8 +258,8 @@ CLEARTEXT - no protection.
if domains: domains = ":" + domains
access_file.write(username + ":" +
generate_passwd(password, encoding) +
domains)
generate_passwd(password, encoding) +
domains)
except "CommandLineError":
sys.stderr.write(usage)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment