Commit a303a379 authored by Julien Muchembled's avatar Julien Muchembled

Implement authentication to Git repositories from ERP5VCS interface

parent 90d367e6
......@@ -66,7 +66,7 @@ return traverseName(context, caller)(**caller_kw)\n
</item>
<item>
<key> <string>id</string> </key>
<value> <string>BusinessTemplate_doSvnLogin</string> </value>
<value> <string>BusinessTemplate_doVcsLogin</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -50,7 +50,8 @@
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>from Products.ERP5VCS.SubversionClient import SubversionSSLTrustError, SubversionLoginError\n
<value> <string>from Products.ERP5VCS.Git import GitLoginError\n
from Products.ERP5VCS.SubversionClient import SubversionSSLTrustError, SubversionLoginError\n
\n
try:\n
raise exception\n
......@@ -62,6 +63,10 @@ except SubversionLoginError, e:\n
message = \'Server needs authentication, no cookie found\'\n
kw = dict(realm=e.getRealm(), username=context.getVcsTool().getPreferredUsername())\n
method = \'BusinessTemplate_viewSvnLogin\'\n
except GitLoginError, e:\n
message = str(e)\n
kw = dict(remote_url=context.getVcsTool().getRemoteUrl())\n
method = \'BusinessTemplate_viewGitLogin\'\n
\n
context.REQUEST.set(\'portal_status_message\', message)\n
return getattr(context.asContext(**kw), method)(caller=caller, caller_kw=caller_kw)\n
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ERP5Form" module="Products.ERP5Form.Form"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>action</string> </key>
<value> <string>BusinessTemplate_doVcsLogin</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>edit_order</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>enctype</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>group_list</string> </key>
<value>
<list>
<string>left</string>
<string>right</string>
<string>center</string>
<string>bottom</string>
<string>hidden</string>
</list>
</value>
</item>
<item>
<key> <string>groups</string> </key>
<value>
<dictionary>
<item>
<key> <string>bottom</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>center</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>hidden</string> </key>
<value>
<list/>
</value>
</item>
<item>
<key> <string>left</string> </key>
<value>
<list>
<string>remote_url</string>
<string>your_user</string>
<string>your_password</string>
<string>your_auth</string>
</list>
</value>
</item>
<item>
<key> <string>right</string> </key>
<value>
<list/>
</value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>BusinessTemplate_viewGitLogin</string> </value>
</item>
<item>
<key> <string>method</string> </key>
<value> <string>POST</string> </value>
</item>
<item>
<key> <string>name</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>pt</string> </key>
<value> <string>vcs_dialog</string> </value>
</item>
<item>
<key> <string>row_length</string> </key>
<value> <int>4</int> </value>
</item>
<item>
<key> <string>stored_encoding</string> </key>
<value> <string>UTF-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Login</string> </value>
</item>
<item>
<key> <string>unicode_mode</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>update_action</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>update_action_title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>default</string>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>remote_url</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>default</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Remote URL</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="TALESMethod" module="Products.Formulator.TALESField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>here/remote_url</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
<string>hidden</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_auth</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>remote_url</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>BusinessTemplate_viewGitLogin</string> </value>
</item>
<item>
<key> <string>hidden</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string encoding="cdata"><![CDATA[
&nbsp;
]]></string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>your_user</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_string_field</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>User Name</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -35,7 +35,7 @@
</item>
<item>
<key> <string>action</string> </key>
<value> <string>BusinessTemplate_doSvnLogin</string> </value>
<value> <string>BusinessTemplate_doVcsLogin</string> </value>
</item>
<item>
<key> <string>description</string> </key>
......@@ -98,7 +98,7 @@
</item>
<item>
<key> <string>name</string> </key>
<value> <string>BusinessTemplate_viewSvnLogin2</string> </value>
<value> <string></string> </value>
</item>
<item>
<key> <string>pt</string> </key>
......
656
\ No newline at end of file
657
\ No newline at end of file
......@@ -28,6 +28,7 @@
import os, re, subprocess
from AccessControl import ClassSecurityInfo
from AccessControl.SecurityInfo import ModuleSecurityInfo
from Acquisition import aq_base
from DateTime import DateTime
from Products.ERP5Type.Message import translateString
......@@ -35,12 +36,19 @@ from ZTUtils import make_query
from Products.ERP5VCS.WorkingCopy import \
WorkingCopy, NotAWorkingCopyError, NotVersionedError, Dir, File, selfcached
# TODO: write a similar helper for 'nt' platform
GIT_ASKPASS = os.path.join(os.path.dirname(__file__), 'bin', 'git_askpass')
class GitError(EnvironmentError):
def __init__(self, err, out, returncode):
EnvironmentError.__init__(self, err)
self.stdout = out
self.returncode = returncode
class GitLoginError(EnvironmentError):
"""Raised when an authentication is required"""
ModuleSecurityInfo(__name__).declarePublic('GitLoginError')
class Git(WorkingCopy):
security = ClassSecurityInfo()
......@@ -48,6 +56,8 @@ class Git(WorkingCopy):
reference = 'git'
title = 'Git'
_login_cookie_name = 'erp5_git_login'
def _git(self, *args, **kw):
kw.setdefault('cwd', self.working_copy)
argv = ['git']
......@@ -64,6 +74,41 @@ class Git(WorkingCopy):
return out.strip()
return out
@selfcached
def _getLogin(self):
target_url = self.getRemoteUrl()
try:
for url, user, password in self._getCookie(self._login_cookie_name, ()):
if target_url == url:
return user, password
except ValueError:
pass
def setLogin(self, remote_url, user, password):
"""Set login information"""
login_list = [x for x in self._getCookie(self._login_cookie_name, ())
if x[0] != remote_url]
login_list.append((remote_url, user, password))
self._setCookie(self._login_cookie_name, login_list)
def remote_git(self, *args, **kw):
try:
env = kw['env']
except KeyError:
kw['env'] = env = dict(os.environ)
env['GIT_ASKPASS'] = GIT_ASKPASS
userpwd = self._getLogin()
if userpwd:
env.update(ERP5_GIT_USERNAME=userpwd[0], ERP5_GIT_PASSWORD=userpwd[1])
try:
return self.git(*args, **kw)
except GitError, e:
message = 'Authentication failed'
if message in str(e):
raise GitLoginError(userpwd and message or
'Server needs authentication, no cookie found')
raise
def __init__(self, *args, **kw):
WorkingCopy.__init__(self, *args, **kw)
out = self._git('rev-parse', '--show-toplevel', '--show-prefix',
......@@ -206,7 +251,7 @@ class Git(WorkingCopy):
raise NotImplementedError
if not keep:
self.clean()
self.git('pull', '--ff-only')
self.remote_git('pull', '--ff-only')
elif 1: # elif local_changes:
raise NotImplementedError
# addremove
......@@ -219,7 +264,7 @@ class Git(WorkingCopy):
# finally:
# symbolic-ref HEAD B
else:
self.git('pull', '--ff-only')
self.remote_git('pull', '--ff-only')
return self.aq_parent.download(self.working_copy)
def showOld(self, path):
......@@ -279,14 +324,14 @@ class Git(WorkingCopy):
remote, dst = remote.split('/', 1)
push_args = 'push', '--porcelain', remote, '%s:%s' % (src, dst)
try:
self.git(*push_args)
self.remote_git(*push_args)
except GitError, e:
# first check why we could not push
status = [x for x in e.stdout.splitlines() if x[:1] == '!']
if (len(status) != 1 or
status[0].split()[2:] != ['[rejected]', '(non-fast-forward)']):
raise
self.git('fetch', '--prune', remote)
self.remote_git('fetch', '--prune', remote)
if not self.getBehindCount():
raise
# try to update our working copy
......@@ -305,7 +350,7 @@ class Git(WorkingCopy):
if merge == 'merge':
reset += 1
# retry to push everything
self.git(*push_args)
self.remote_git(*push_args)
except GitError, e:
self.git('reset', '--soft', '@{%u}' % reset)
portal_status_message = str(e)
......
......@@ -29,9 +29,7 @@
#
##############################################################################
import errno, glob, json, os, re, shutil
from base64 import b64encode, b64decode
from DateTime import DateTime
import errno, glob, os, re, shutil
from ZTUtils import make_query
from Products.ERP5Type.Message import translateString
from Products.ERP5.Document.BusinessTemplate import BusinessTemplateFolder
......@@ -55,22 +53,6 @@ class Subversion(WorkingCopy):
if path and not os.path.exists(os.path.join(self.working_copy, '.svn')):
raise NotAWorkingCopyError(self.working_copy)
def _getCookie(self, name, default=None):
try:
return json.loads(b64decode(self.REQUEST[name]))
except StandardError:
return default
def _setCookie(self, name, value, days=30):
portal = self.getPortalObject()
request = portal.REQUEST
value = b64encode(json.dumps(value))
request.set(name, value)
if days:
expires = (DateTime() + days).toZone('GMT').rfc822()
request.RESPONSE.setCookie(name, value, path=portal.absolute_url_path(),
expires=expires)
def setLogin(self, realm, user, password):
"""Set login information.
"""
......
......@@ -29,13 +29,15 @@
#
##############################################################################
import errno, os, re, shutil
import errno, json, os, re, shutil
from base64 import b64encode, b64decode
from tempfile import gettempdir
import transaction
from AccessControl import Unauthorized
from AccessControl.SecurityInfo import ModuleSecurityInfo
from Acquisition import aq_base, Implicit
from App.config import getConfiguration
from DateTime import DateTime
from ZTUtils import make_query
from Products.ERP5.Document.BusinessTemplate import BusinessTemplateFolder
from Products.ERP5Type.Utils import simple_decorator
......@@ -141,6 +143,22 @@ class WorkingCopy(Implicit):
raise Unauthorized("Unauthorized access to path %r."
" It is NOT in your Zope home instance." % path)
def _getCookie(self, name, default=None):
try:
return json.loads(b64decode(self.REQUEST[name]))
except StandardError:
return default
def _setCookie(self, name, value, days=30):
portal = self.getPortalObject()
request = portal.REQUEST
value = b64encode(json.dumps(value))
request.set(name, value)
if days:
expires = (DateTime() + days).toZone('GMT').rfc822()
request.RESPONSE.setCookie(name, value, path=portal.absolute_url_path(),
expires=expires)
# path is the path in svn working copy
# return edit_path in zodb to edit it
def editPath(self, path, html=False):
......
#!/bin/sh -e
case "$1" in
Username:\ ) echo -n "$ERP5_GIT_USERNAME" ;;
Password:\ ) echo -n "$ERP5_GIT_PASSWORD" ;;
*) false ;;
esac
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