Commit a4c67f4b authored by Kazuhiko Shiozaki's avatar Kazuhiko Shiozaki Committed by Vincent Pelletier

Use a dedicated Login document, separate user id from Person's reference.

# Situation before

* Person reference is a convenient property to look Persons up with, because
  it has an efficient secondary index in catalog table, and is part of fulltext
  representation of all ERP5 documents (including Persons).

- Person reference is user's login, ie what users type to identify
  themselves.

- Person reference is user's id, a widely unknown concept which is how local
  roles are granted (including ownership)

User authentication and enumeration are handled by PAS plugin
`ERP5 User Manager`.

# Problems

- We need more than one way to identify oneself: login/password, but also
  3rd-party services (facebook, google, persona, client x509 certificates,
  various types of tokens, ...) but the current mecanism is monovalued.

- We cannot change a Person reference once its set without risking striping
  the user from all its local roles everywhere in the site, with no cheap way to
  identify what gave a role to a given user.

- We may need to change a user's login (because of policy change, ...).

# Situation after

- Person reference remains a convenient property to look persons up with.No
  change here.

- ERP5 Login is a new document type allowed inside Person documents. Each
  identifying mechanism can have its own document type with properties suiting
  its purpose, and any number of instance inside a single Person. Logins have a
  workflow state allowing to control their life span and keep some traceability
  (via workflow histories).
  ERP5 Login.getReference is user's login.
  Because a user can change his own login, there is no long term relation between login and user.

- Person use_id is user's id, which is systematically assigned upon Person
  creation. Any document which is a Zope user must be bound to the new ERP5User
  property sheet.

This new pattern is supported by a new PAS plugin for authentication and
enumeration: `ERP5 Login User Manager`.

# Minimal upgrade

Change your test suite to create the old PAS plugin and disable the new one.

After upgrading erp5_mysql_innodb_catalog, create the "user" table if it does
not happen automatically.

Nothing else to do. The code is exepcted to be backward-compatible with the
historical data schema. Just make sure you keep the original PAS plugin enabled
and you should be fine.

Be advised though that the old data schema may not be tested much, which means
compatibility may get broken.

# Full upgrade

Get your unit tests to pass with the new PAS plugin. This includes getting your
Person-creating, -modifying and -querying code to use the proper APIs, and to migrate all your workflow `actor` variable declarations to use `getUserIdOrUserName`.

After upgrading erp5_mysql_innodb_catalog, create the "user" table if it does
not happen automatically.

Once you are done upgrading, start automated Person migration. It will:
- create the new PAS plugin

- gradually create ERP5 Login documents, copy the existing Person reference to
  user_id to not break existing users

- once done, deleted the old PAS plugin

## Caveats

- for each migrated user there is a small time lapse (the duration of a few
  reindexations) where it is not a user

- better migrate a few persons individually while checking roles_and_users table
  content, to ensure no new security uids get allocated in that process. It may
  happen if view-granting roles are granted to users on their own Person
  documents, for example, and may expose security configuration weaknesses to
  race-conditions on local role setting and indexation. If your security is only
  group-based, nothing to fear.

- the migration is rather fast, but in the end all depends on how many Person
  documents you have. On a beefy instance (15 processing nodes on a single
  large server) 450k Persons were migrated in about 20 hours, or 6 persons a
  second, along with normal instance usage.

# APIs

The following APIs are available, independently of whether you migrated your
users or not. Use them.

- About the current user:

    *Do* use User API (as conveniently accessed through portal_membership), which
    is a convenient shortcut to the PAS API.

    ```python
    user = context.getPortalObject().portal_membership.getAuthenticatedMember()
    # The user document (typically, a Person)
    user.getUserValue()
    # The login document currently used (be careful, it may contain plain-text
    # secret values, like a token).
    user.getLoginValue()
    ```

    Do *not* peek in the request.

    Do *not* use the catalog.

    Do *not* search in person_module.

- About another user:

    *Do* use PAS API.

    For reasons which should be obvious, you should generally *not* descend in a
    specific PAS plugin.

    ```python
    # Many users:
    user_list = context.acl_users.searchUser(
      id=user_id_list,
      exact_match=True,
    )
    # Many logins:
    user_list = context.acl_users.searchUser(
      login=user_login_list,
      exact_match=True,
    )
    # Be careful of cases where a single user may be enumerated by more than one
    # PAS plugin. Depending on what you want to access among user properties
    # the applicable pattern will change. Consider the following, which will not
    # tolerate homonym-but-different users (which is a good thing !):
    user_path, = {
      x['path'] for x in context.acl_users.searchUser(
        id=user_id,
        exact_match=True,
      ) if 'path' in x
    }
    # When searching for a single login, you should not tolerate receiving more
    # than one user, with more than one login:
    user_list = [
      x for x in context.acl_users.searchUser(
        id=user_id,
        exact_match=True,
      ) if 'login_list' in x
    ]
    if user_list:
      login, = user_list['login_list']
      # Do something on the login
    else:
      # Login does not exist, do something else
    ```
    Each item in `user_list` contains the following keys:

    - `id`: the user id (ex: `Person.getUserId()`'s return value)

    - `path`: user document's path, if you want the document

    - `uid`: user document's uid, if you want to search further using the catalog

    - `login_list`, itself containing, one per user's login matching the conditions

      - `reference`: the textual login value (or token, or..., whatever PAS should
        use to lookup a user from transmitted credentials)

      - `path`: login document's path, if you want the document

      - `uid`: login document's uid, if you want to search further using the catalog

    If you are on an instance with non-migrated users, `login_list` will have a single entry, which will point at the Person document itself (just as the `user` level).

- About a Person:

    *Do* use catalog API (`portal_catalog`, `searchFolder`, `ZSQLMethod`s, ...).

    Do *not* use PAS API.

- To know the user_id of a Person document:

    ```python
    person.Person_getUserId()
    ```

- To know the login of a user which is not currently logged in:

    This need is weird. Are you sure you did not forget to ask the user for their login ? If not, you're going to have to assume things about how many login documents the user has, which will not be well compatible.

# Cheat-sheets (but use your common sense !)

| is it a good idea to [action on the right] on [value below] |  store hashed & salted | store plaintext | display | look document (user or login) up by |
|---|---|---|---|---|
| password | yes | no | no | not possible |
| username | no | yes | no, a username may be a token, which is both a login and a password | only when really doing login-related stuff (ex: password recovery, logging a user in...), using PAS API |
| token | no | yes | no |  only when really doing login-related stuff (ex: password recovery, logging a user in...), using PAS API |
| user id | no | yes | yes (if user complains it not a nice value, use reference) | yes, using PAS API |
| reference  | no | yes | yes | yes, using catalog API |

| to refer to [notion below] can I use [user property on the right] | User's `id` | User's `user name` (aka login) | an ERP5 document property (`title`, `reference`, ...) |
| --- | --- | --- | --- |
| a workflow actor (as stored) | yes | **no** | **no** |
| a workflow actor (as displayed) | yes (may be ugly) | **no** | yes |
| the owner of a document (as stored) | yes | **no** | **no** |
| granting a local role on a document (as stored) | yes | **no** | **no** |
| the owner of a document (as displayed) | yes (may be ugly) | **no** | yes |
| logged in user (displayed) | yes (may be ugly) | **no** | yes |
| user having generated a document (displayed, even in the document) | yes (may be ugly) | **no** | yes |
| the requester of a password reset ("I forgot my password") request (as stored) | **no** | **no** | yes, some relation to the document (`path`, `relative_url`...) |
| the requester of a password reset ("I forgot my password") request (as input by user) | **no** | yes | yes, optionally  and always in addition to User's `user name`  (to prevent an attacker from making us spam users) |
| the requester of an account recovery ("I forgot my login") request (as stored) | **no** | **no** | yes, some relation to the document (`path`, `relative_url`...) |
| the requester of an account recovery ("I forgot my login") request (as input by user) | **no** | **no** | yes |
| the user in another system | **no** | **no** | yes ({`source`,`destination`}`_reference` are your friends) |

Put in another way:
- If it's stored in ERP5 and used by security machinery, you want to use the `id`
- if it's stored in ERP5 and used by the document machinery, you want to use a relation to the document (ex: `Person` for a User `id`, `ERP5 Login` for a User user name)
- If it's stored in another system (ie, part of an interface), use a document property
- If it's displayed to the user, use a document property (optionally falling back on the User `id`)

And in tests ? Unless you are testing an authentication mechanism, use User `id` only: It's unique, it's generated for you by ERP5 anyway, it does not need creating & validating an ERP5 Login document, it does not need choosing a login, it does not need choosing a password. Existing tests use a mix of techniques, please ignore them (or better, update them to use the simpler scheme !).

/reviewed-on nexedi/erp5!185
parents 7a8047f4 0ae4ed40
......@@ -3,7 +3,7 @@
from Products.ERP5Type.JSONEncoder import encodeInJson as dumps
portal = context.getPortalObject()
reference = portal.portal_membership.getAuthenticatedMember().getUserName()
reference = portal.portal_membership.getAuthenticatedMember().getIdOrUserName()
processing = None
if reference == "Anonymous User":
......
......@@ -2,7 +2,7 @@
portal = context.getPortalObject()
member = portal.portal_membership.getAuthenticatedMember()
reference = member.getUserName()
reference = member.getIdOrUserName()
if reference == "Anonymous User":
return context.WebSection_viewUploadFileDialog()
else:
......
......@@ -14,7 +14,7 @@ if access_token_document.getValidationState() == 'validated':
agent_document = access_token_document.getAgentValue()
if agent_document is not None:
result = agent_document.getReference(None)
result = agent_document.Person_getUserId()
comment = "Token usage accepted"
access_token_document.invalidate(comment=comment)
......
......@@ -22,6 +22,6 @@ if access_token_document.getValidationState() == 'validated':
agent_document = access_token_document.getAgentValue()
if agent_document is not None:
result = agent_document.getReference(None)
result = agent_document.Person_getUserId()
return result
......@@ -45,7 +45,7 @@ class TestERP5AccessTokenSkins(ERP5TypeTestCase):
indexing is done. """
person_module = self.getPersonModule()
person = person_module.newContent(portal_type='Person',
reference='TESTP-' + new_id)
user_id='TESTP-' + new_id)
person.newContent(portal_type = 'Assignment').open()
transaction.commit()
return person
......@@ -91,7 +91,7 @@ class TestERP5AccessTokenSkins(ERP5TypeTestCase):
self.portal.REQUEST.form["access_token_secret"] = access_token.getReference()
result = self._getTokenCredential(self.portal.REQUEST)
self.assertEqual(result.get('external_login'), person.getReference())
self.assertEqual(result.get('external_login'), person.Person_getUserId())
def test_bad_token(self):
person = self.person = self._createPerson(self.new_id)
......@@ -129,7 +129,7 @@ class TestERP5AccessTokenSkins(ERP5TypeTestCase):
result = access_token.RestrictedAccessToken_getExternalLogin()
self.assertEqual(result, person.getReference())
self.assertEqual(result, person.Person_getUserId())
self.assertEqual(access_token.getValidationState(), 'validated')
def test_RestrictedAccessToken_getExternalLogin_access_token_secret(self):
......@@ -156,7 +156,7 @@ class TestERP5AccessTokenSkins(ERP5TypeTestCase):
result = access_token.RestrictedAccessToken_getExternalLogin()
self.assertEqual(result, person.getReference())
self.assertEqual(result, person.Person_getUserId())
self.assertEqual(access_token.getValidationState(), 'validated')
def test_RestrictedAccessToken_getExternalLogin_no_agent(self):
......@@ -226,7 +226,7 @@ class TestERP5AccessTokenSkins(ERP5TypeTestCase):
result = access_token.OneTimeRestrictedAccessToken_getExternalLogin()
self.assertEqual(result, person.getReference())
self.assertEqual(result, person.Person_getUserId())
self.assertEqual(access_token.getValidationState(), 'invalidated')
def test_OneTimeRestrictedAccessToken_getExternalLogin_wrong_values(self):
......
......@@ -89,7 +89,7 @@ item_list = getItemList( category=category,
portal_path=context.getPortalObject().getPhysicalPath(),
mirror=mirror,
omit_filter=omit_filter, # XXX possible optim: only one cache if omit_filter
user_name=str(getSecurityManager().getUser()),
user_name=getSecurityManager().getUser().getIdOrUserName(),
simulation_state=simulation_state)
# make sure that the current value is included in this list, this is
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -22,7 +22,7 @@ context.activate().AccountingTransactionModule_viewFrenchAccountingTransactionFi
at_date,
simulation_state,
ledger,
user_name=person_value.getReference(),
user_name=person_value.Person_getUserId(),
tag=tag,
aggregate_tag=aggregate_tag)
......
......@@ -6,7 +6,7 @@
<pagetemplate startframe='content' id='FirstPage'>
<static>
<infostring align="left" x="1cm" y= "29cm" size="8" font="Helvetica" color="(0,0,0)"
tal:content="python: here.Base_translateString('ui', 'Printed by %(user)s at %(date)s') % {'user':user.getUserName(), 'date':DateTime()}" >Printed by</infostring>
tal:content="python: here.Base_translateString('ui', 'Printed by %(user)s at %(date)s') % {'user':user.getIdOrUserName(), 'date':DateTime()}" >Printed by</infostring>
<infostring align="left" x="18cm" y= "0.5cm" size="10" font="Helvetica" color="(0,0,0)" >Page %(page)s</infostring>
</static>
<frame showBoundary='0' leftpadding='0.1cm' height='23.94cm' width='17.59cm' rightpadding='0.1cm' y='2cm' x='2cm' nextid='content' toppadding='0.2cm' id='content' bottompadding='0.5cm'/>
......
......@@ -30,7 +30,7 @@ for entity in ( portal.organisation_module.objectValues() +
# enable preference
ptool = portal.portal_preferences
pref = ptool.accounting_zuite_preference
if pref.owner_info()['id'] != str(context.REQUEST.AUTHENTICATED_USER):
if pref.owner_info()['id'] != context.REQUEST.AUTHENTICATED_USER.getId():
# we have to 'own' the preference for the test
ptool = portal.portal_preferences
# pref.setId('old_accounting_zuite_preference')
......
......@@ -83,7 +83,9 @@ class TestERP5Administration(InventoryAPITestCase):
def test_check_consistency_alarm(self):
alarm = self.portal.portal_alarms.check_consistency
person = self.portal.person_module.newContent(portal_type='Person')
# Here we disable user_id so that Person_createUserPreference will not be called
# automatically.
person = self.portal.person_module.newContent(portal_type='Person', user_id=None)
# this document will be non consistent, for PropertyTypeValidity
person.title = 3
# tic right now to make sure the person is indexed, indeed the alarm
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -56,7 +56,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -56,7 +56,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -56,7 +56,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
<workflow_chain>
<chain>
<type>Person</type>
<type>ERP5 Login</type>
<workflow>password_interaction_workflow</workflow>
</chain>
</workflow_chain>
\ No newline at end of file
......@@ -62,7 +62,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_analyzePassword</string> </value>
<value> <string>Login_analyzePassword</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_getListboxUrl</string> </value>
<value> <string>Login_getListboxUrl</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -62,7 +62,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_isLoginBlocked</string> </value>
<value> <string>Login_isLoginBlocked</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -62,7 +62,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_isPasswordExpired</string> </value>
<value> <string>Login_isPasswordExpired</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -13,27 +13,13 @@ message_dict = { 0: 'Unknown error',
-4: 'You have already used this password.',
-5: 'You can not use any parts of your first and last name in password.'}
def doValidation(person, password):
def doValidation(login, password):
# raise so Formulator shows proper message
result_code_list = person.Person_analyzePassword(password)
result_code_list = login.analyzePassword(password)
if result_code_list!=[]:
translateString = context.Base_translateString
message = ' '.join([translateString(message_dict[x]) for x in result_code_list])
raise ValidationError('external_validator_failed', context, error_text=message)
return 1
user_login = request.get('field_user_login', None)
# find Person object (or authenticated member) and validate it on it (password recovered for an existing account)
person = context.ERP5Site_getAuthenticatedMemberPersonValue(user_login)
if person is not None:
return doValidation(person, password)
# use a temp object (new account created)
first_name = request.get('field_your_first_name', None)
last_name = request.get('field_your_last_name', None)
kw = {'title': '%s %s' %(first_name, last_name),
'first_name': first_name,
'last_name': last_name}
person = newTempBase(portal, kw['title'], **kw)
return doValidation(person, password)
return doValidation(context, password)
......@@ -62,7 +62,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Base_isPasswordValid</string> </value>
<value> <string>Login_isPasswordValid</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -63,7 +63,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_notifyLoginFailure</string> </value>
<value> <string>Login_notifyLoginFailure</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -63,7 +63,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_notifyPasswordExpire</string> </value>
<value> <string>Login_notifyPasswordExpire</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -54,7 +54,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_unblockLogin</string> </value>
<value> <string>Login_unblockLogin</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -35,6 +35,6 @@ for person, failure_list in all_blocked_user_login_dict.items():
person.getTitle(),
**{'title': person.getTitle(),
'count':len(failure_list),
'reference': person.getReference(),
'reference': person.Person_getUserId(),
'url': person.absolute_url()}))
return blocked_user_login_list
......@@ -174,15 +174,15 @@
<list>
<tuple>
<string>title</string>
<string>Person_getListboxUrl</string>
<string>Login_getListboxUrl</string>
</tuple>
<tuple>
<string>reference</string>
<string>Person_getListboxUrl</string>
<string>Login_getListboxUrl</string>
</tuple>
<tuple>
<string>count</string>
<string>Person_getListboxUrl</string>
<string>Login_getListboxUrl</string>
</tuple>
</list>
</value>
......
......@@ -28,7 +28,7 @@
<key> <string>after_script_name</string> </key>
<value>
<list>
<string>Person_changePassword</string>
<string>afterChangePassword</string>
</list>
</value>
</item>
......@@ -72,10 +72,16 @@
<key> <string>portal_type_filter</string> </key>
<value>
<list>
<string>Person</string>
<string>ERP5 Login</string>
</list>
</value>
</item>
<item>
<key> <string>portal_type_group_filter</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>script_name</string> </key>
<value>
......
from DateTime import DateTime
portal = context.getPortalObject()
person = state_change['object']
login = state_change['object']
portal = login.getPortalObject()
# check preferences and save only if set
number_of_last_password_to_check = portal.portal_preferences.getPreferredNumberOfLastPasswordToCheck()
if number_of_last_password_to_check is not None and number_of_last_password_to_check:
# save password and modification date
current_password = person.getPassword()
current_password = login.getPassword()
if current_password is not None:
password_event = portal.system_event_module.newContent(portal_type = 'Password Event',
source_value = person,
destination_value = person,
password = current_password)
password_event = portal.system_event_module.newContent(portal_type='Password Event',
source_value=login,
destination_value=login,
password=current_password)
password_event.confirm()
# Person_isPasswordExpired cache the wrong result if document is not in catalog.
# As the document is created in the same transaction, it is possible to force reindexation
......
......@@ -63,7 +63,7 @@
</item>
<item>
<key> <string>id</string> </key>
<value> <string>Person_changePassword</string> </value>
<value> <string>afterChangePassword</string> </value>
</item>
</dictionary>
</pickle>
......
Person | password_interaction_workflow
\ No newline at end of file
ERP5 Login | password_interaction_workflow
\ No newline at end of file
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -6,7 +6,7 @@ return destination
# OLD METHOD user logged in
#user_id = context.portal_membership.getAuthenticatedMember().getUserName()
#user_id = context.portal_membership.getAuthenticatedMember().getId()
# NEW METHOD must use owner to know site letter
old_group_list = context.get_local_roles()
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -14,7 +14,7 @@ resource = transaction.CashDelivery_checkCounterInventory(source = destination,
#if len(vliste) != 0:
# raise ValidationFailed, (vliste[0].getMessage(),)
user_id = transaction.portal_membership.getAuthenticatedMember().getUserName()
user_id = transaction.portal_membership.getAuthenticatedMember().getId()
site_list = context.Baobab_getUserAssignedSiteList(user_id=user_id)
# context.log('validateVaultBalance site_list',site_list)
destination = transaction.getDestination()
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -5,7 +5,7 @@ transaction = state_change['object']
# Compute the source form the vault choosen by
# the accountant and find the counter with the
# user logged in
user_id = transaction.portal_membership.getAuthenticatedMember().getUserName()
user_id = transaction.portal_membership.getAuthenticatedMember().getId()
site_list = context.Baobab_getUserAssignedSiteList(user_id=user_id)
# context.log('validateVaultBalance site_list',site_list)
source = transaction.getSource()
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName </string> </value>
<value> <string>user/getIdOrUserName </string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -3,7 +3,7 @@
from Products.ERP5Type.Cache import CachingMethod
if user_id is None:
user_id = context.portal_membership.getAuthenticatedMember().getUserName()
user_id = context.portal_membership.getAuthenticatedMember().getId()
def getFunctionList(user_id=user_id):
......
......@@ -3,7 +3,7 @@
from Products.ERP5Type.Cache import CachingMethod
if user_id is None:
user_id = context.portal_membership.getAuthenticatedMember().getUserName()
user_id = context.portal_membership.getAuthenticatedMember().getId()
def getGroupList(user_id=user_id):
......
......@@ -3,7 +3,7 @@
from Products.ERP5Type.Cache import CachingMethod
if user_id is None:
user_id = context.portal_membership.getAuthenticatedMember().getUserName()
user_id = context.portal_membership.getAuthenticatedMember().getId()
def getSiteList(user_id=user_id):
......
from Products.ERP5Type.Cache import CachingMethod
user_id = context.portal_membership.getAuthenticatedMember().getUserName()
user_id = context.portal_membership.getAuthenticatedMember().getId()
def getUserSiteUid(user_id):
return context.Baobab_getUserAssignedRootSite(user_id=user_id, object=1).getSiteUid()
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -59,7 +59,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>user/getUserName</string> </value>
<value> <string>user/getIdOrUserName</string> </value>
</item>
</dictionary>
</pickle>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ActionInformation" module="Products.CMFCore.ActionInformation"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>categories</string> </key>
<value>
<tuple>
<string>action_type/object_view</string>
</tuple>
</value>
</item>
<item>
<key> <string>category</string> </key>
<value> <string>object_view</string> </value>
</item>
<item>
<key> <string>condition</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>icon</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>view</string> </value>
</item>
<item>
<key> <string>permissions</string> </key>
<value>
<tuple>
<string>View</string>
</tuple>
</value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Action Information</string> </value>
</item>
<item>
<key> <string>priority</string> </key>
<value> <float>1.0</float> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>View</string> </value>
</item>
<item>
<key> <string>visible</string> </key>
<value> <int>1</int> </value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="Expression" module="Products.CMFCore.Expression"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/Login_view</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
##############################################################################
#
# Copyright (c) 2015 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
import zope.interface
from AccessControl import ClassSecurityInfo
from Products.ERP5.mixin.encrypted_password import EncryptedPasswordMixin
from Products.ERP5.mixin.login_account_provider import LoginAccountProviderMixin
from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Type.XMLObject import XMLObject
class Login(XMLObject, LoginAccountProviderMixin, EncryptedPasswordMixin):
meta_type = 'ERP5 Login'
portal_type = 'Login'
add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.INode)
# Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properties
property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject
, PropertySheet.CategoryCore
, PropertySheet.DublinCore
, PropertySheet.Reference
, PropertySheet.Login
, PropertySheet.LoginConstraint
)
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Document Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>Login</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>document.erp5.Login</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Document Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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