diff --git a/.bumpversion.cfg b/.bumpversion.cfg --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 4.14.1 +current_version = 4.15.0 message = release: Bump version {current_version} to {new_version} [bumpversion:file:rhodecode/VERSION] diff --git a/.release.cfg b/.release.cfg --- a/.release.cfg +++ b/.release.cfg @@ -5,25 +5,20 @@ done = false done = true [task:rc_tools_pinned] -done = true [task:fixes_on_stable] -done = true [task:pip2nix_generated] -done = true [task:changelog_updated] -done = true [task:generate_api_docs] -done = true + +[task:updated_translation] [release] -state = prepared -version = 4.14.1 - -[task:updated_translation] +state = in_progress +version = 4.15.0 [task:generate_js_routes] diff --git a/README.rst b/README.rst --- a/README.rst +++ b/README.rst @@ -7,18 +7,20 @@ About ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_ and Subversion_ with a built in push/pull server, full text search, -pull requests and powerful code-review system. It works on http/https and +pull requests and powerful code-review system. It works on http/https, SSH and has a few unique features like: - - plugable architecture - - advanced permission system with IP restrictions - - rich set of authentication plugins including LDAP, - ActiveDirectory, Atlassian Crowd, Http-Headers, Pam, Token-Auth. - - live code-review chat - - full web based file editing - - unified multi vcs support - - snippets (gist) system - - integration with all 3rd party issue trackers +- plugable architecture from Pyramid web-framework. +- advanced permission system with IP restrictions, inheritation, and user-groups. +- rich set of authentication plugins including LDAP, ActiveDirectory, SAML 2.0, + Atlassian Crowd, Http-Headers, Pam, Token-Auth, OAuth. +- live code-review chat, and reviewer rules. +- full web based file editing. +- unified multi vcs support. +- snippets (gist) system. +- integration framework for Slack, CI systems, Webhooks. +- integration with all 3rd party issue trackers. + RhodeCode also provides rich API, and multiple event hooks so it's easy integrable with existing external systems. diff --git a/configs/development.ini b/configs/development.ini --- a/configs/development.ini +++ b/configs/development.ini @@ -1,10 +1,11 @@ ################################################################################ -## RHODECODE COMMUNITY EDITION CONFIGURATION ## +## RHODECODE COMMUNITY EDITION CONFIGURATION ## ################################################################################ [DEFAULT] +## Debug flag sets all loggers to debug, and enables request tracking debug = true ################################################################################ @@ -414,6 +415,7 @@ search.location = %(here)s/data/index ######################################## ## channelstream enables persistent connections and live notification ## in the system. It's also used by the chat system + channelstream.enabled = false ## server address for channelstream server on the backend @@ -490,14 +492,6 @@ appenlight.request_keys_blacklist = ## (by default the client ignores own entries: appenlight_client.client) appenlight.log_namespace_blacklist = - -################################################################################ -## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ## -## Debug mode will enable the interactive debugging tool, allowing ANYONE to ## -## execute malicious code after an exception is raised. ## -################################################################################ -#set debug = false - # enable debug style page debug_style = true diff --git a/configs/production.ini b/configs/production.ini --- a/configs/production.ini +++ b/configs/production.ini @@ -1,11 +1,12 @@ ################################################################################ -## RHODECODE COMMUNITY EDITION CONFIGURATION ## +## RHODECODE COMMUNITY EDITION CONFIGURATION ## ################################################################################ [DEFAULT] -debug = true +## Debug flag sets all loggers to debug, and enables request tracking +debug = false ################################################################################ ## EMAIL CONFIGURATION ## @@ -389,6 +390,7 @@ search.location = %(here)s/data/index ######################################## ## channelstream enables persistent connections and live notification ## in the system. It's also used by the chat system + channelstream.enabled = false ## server address for channelstream server on the backend @@ -466,14 +468,6 @@ appenlight.request_keys_blacklist = appenlight.log_namespace_blacklist = -################################################################################ -## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ## -## Debug mode will enable the interactive debugging tool, allowing ANYONE to ## -## execute malicious code after an exception is raised. ## -################################################################################ -set debug = false - - ########################################### ### MAIN RHODECODE DATABASE CONFIG ### ########################################### @@ -524,6 +518,7 @@ vcs.scm_app_implementation = http ## Push/Pull operations hooks protocol, available options are: ## `http` - use http-rpc backend (default) vcs.hooks.protocol = http + ## Host on which this instance is listening for hooks. If vcsserver is in other location ## this should be adjusted. vcs.hooks.host = 127.0.0.1 diff --git a/docs/admin/adding-anonymous-user.rst b/docs/admin/adding-anonymous-user.rst --- a/docs/admin/adding-anonymous-user.rst +++ b/docs/admin/adding-anonymous-user.rst @@ -3,19 +3,19 @@ Anonymous Users --------------- -By default, |RCM| provides |repo| access for registered users only. It can be +By default, |RCE| provides |repo| access for registered users only. It can be configured to be **world-open** in terms of read and write permissions. This -configuration is called "Anonymous Access" and allows |RCM| to be used as a +configuration is called "Anonymous Access" and allows |RCE| to be used as a public hub where unregistered users have access to your |repos|. Anonymous access is useful for open source projects, universities, or if running inside a restricted internal corporate network to serve documents to all employees. Anonymous users get the default user permission -settings that are applied across the whole |RCM| system. +settings that are applied across the whole |RCE| system. To enable anonymous access to your |repos|, use the following steps: -1. From the |RCM| interface, select :menuselection:`Admin --> Permissions`. +1. From the |RCE| interface, select :menuselection:`Admin --> Permissions`. 2. On the Application tab, check the :guilabel:`Allow anonymous access` box. 3. Select :guilabel:`Save`. 4. To set the anonymous user access permissions, which are based on the diff --git a/docs/admin/admin-tricks.rst b/docs/admin/admin-tricks.rst --- a/docs/admin/admin-tricks.rst +++ b/docs/admin/admin-tricks.rst @@ -166,7 +166,7 @@ 2. Add your custom hook details, you can ``pretxnchangegroup.example`` with value ``python:/path/to/custom_hook.py:my_func_name`` 3. Select :guilabel:`Save` -Also, see the |RC| Extensions section of the :ref:`rc-tools` guide. |RC| +Also, see the RhodeCode Extensions section of the :ref:`rc-tools` guide. RhodeCode Extensions can be used to add additional hooks to your instance and comes with a number of pre-built plugins if you chose to install them. diff --git a/docs/admin/apache-wsgi-coding.rst b/docs/admin/apache-wsgi-coding.rst --- a/docs/admin/apache-wsgi-coding.rst +++ b/docs/admin/apache-wsgi-coding.rst @@ -3,7 +3,7 @@ Apache WSGI Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^ -|RCM| can also be set up with Apache under ``mod_wsgi``. To configure this +|RCE| can also be set up with Apache under ``mod_wsgi``. To configure this use the following steps. 1. Install ``mod_wsgi`` using the following command: @@ -50,6 +50,6 @@ The following is an example ``wsgi`` dis .. note:: When using `mod_wsgi` the same version of |hg| must be running in your - system's |PY| environment and on |RCM|. To check the |RCM| version, + system's |PY| environment and on |RCE|. To check the |RCE| version, on the interface go to :menuselection:`Admin --> Settings --> System Info` diff --git a/docs/admin/config-files-overview.rst b/docs/admin/config-files-overview.rst --- a/docs/admin/config-files-overview.rst +++ b/docs/admin/config-files-overview.rst @@ -67,7 +67,7 @@ sections. Default location: :file:`/home/{user}/.rccontrol/cache/MANIFEST` |RCC| uses this file to source the latest available builds from the - secure |RC| download channels. The only reason to mess with this file + secure RhodeCode download channels. The only reason to mess with this file is if you need to do an offline installation, see the :ref:`Offline Installation` instructions, otherwise |RCC| will completely manage this file. diff --git a/docs/admin/enable-debug.rst b/docs/admin/enable-debug.rst --- a/docs/admin/enable-debug.rst +++ b/docs/admin/enable-debug.rst @@ -3,6 +3,16 @@ Enabling Debug Mode ------------------- +Debug Mode will enable debug logging, and request tracking middleware. Debug Mode +enabled DEBUG log-level which allows tracking various information about authentication +failures, LDAP connection, email etc. + +The request tracking will add a special +unique ID: `| req_id:00000000-0000-0000-0000-000000000000` at the end of each log line. +The req_id is the same for each individual requests, it means that if you want to +track particular user logs only, and exclude other concurrent ones +simply grep by `req_id` uuid which you'll have to find for the individual request. + To enable debug mode on a |RCE| instance you need to set the debug property in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. To do this, use the following steps @@ -11,14 +21,10 @@ 1. Open the file and set the ``debug`` l 2. Restart you instance using the ``rccontrol restart`` command, see the following example: -You can also set the log level, the follow are the valid options; -``debug``, ``info``, ``warning``, or ``fatal``. - .. code-block:: ini [DEFAULT] debug = true - pdebug = false .. code-block:: bash @@ -27,6 +33,7 @@ You can also set the log level, the foll Instance "enterprise-1" successfully stopped. Instance "enterprise-1" successfully started. + Debug and Logging Configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -47,7 +54,7 @@ the ``debug`` level. ### LOGGING CONFIGURATION #### ################################ [loggers] - keys = root, sqlalchemy, rhodecode, ssh_wrapper + keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper [handlers] keys = console, console_sql, file, file_rotating @@ -62,11 +69,16 @@ the ``debug`` level. level = NOTSET handlers = console - [logger_routes] + [logger_sqlalchemy] + level = INFO + handlers = console_sql + qualname = sqlalchemy.engine + propagate = 0 + + [logger_beaker] level = DEBUG handlers = - qualname = routes.middleware - ## "level = DEBUG" logs the route matched and routing variables. + qualname = beaker.container propagate = 1 [logger_rhodecode] @@ -75,11 +87,16 @@ the ``debug`` level. qualname = rhodecode propagate = 1 - [logger_sqlalchemy] - level = INFO - handlers = console_sql - qualname = sqlalchemy.engine - propagate = 0 + [logger_ssh_wrapper] + level = DEBUG + handlers = + qualname = ssh_wrapper + propagate = 1 + + [logger_celery] + level = DEBUG + handlers = + qualname = celery ############## ## HANDLERS ## @@ -87,19 +104,19 @@ the ``debug`` level. [handler_console] class = StreamHandler - args = (sys.stderr,) - level = INFO + args = (sys.stderr, ) + level = DEBUG formatter = generic [handler_console_sql] class = StreamHandler - args = (sys.stderr,) - level = WARN + args = (sys.stderr, ) + level = INFO formatter = generic [handler_file] class = FileHandler - args = ('rhodecode.log', 'a',) + args = ('rhodecode_debug.log', 'a',) level = INFO formatter = generic @@ -107,6 +124,25 @@ the ``debug`` level. class = logging.handlers.TimedRotatingFileHandler # 'D', 5 - rotate every 5days # you can set 'h', 'midnight' - args = ('rhodecode.log', 'D', 5, 10,) + args = ('rhodecode_debug_rotated.log', 'D', 5, 10,) level = INFO formatter = generic + + ################ + ## FORMATTERS ## + ################ + + [formatter_generic] + class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter + format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s + datefmt = %Y-%m-%d %H:%M:%S + + [formatter_color_formatter] + class = rhodecode.lib.logging_formatter.ColorFormatter + format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s + datefmt = %Y-%m-%d %H:%M:%S + + [formatter_color_formatter_sql] + class = rhodecode.lib.logging_formatter.ColorFormatterSql + format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s + datefmt = %Y-%m-%d %H:%M:%S \ No newline at end of file diff --git a/docs/admin/glossary.rst b/docs/admin/glossary.rst --- a/docs/admin/glossary.rst +++ b/docs/admin/glossary.rst @@ -33,7 +33,7 @@ Glossary Adding more machines or workers into your pool of resources. Instance - A single installed version of one of the |RC| products. It could + A single installed version of one of the RhodeCode products. It could refer to |RCE| or the VCS server depending on the context. Plugin diff --git a/docs/admin/indexing.rst b/docs/admin/indexing.rst --- a/docs/admin/indexing.rst +++ b/docs/admin/indexing.rst @@ -3,7 +3,7 @@ Full-text Search ---------------- -By default |RC| is configured to use `Whoosh`_ to index |repos| and +By default RhodeCode is configured to use `Whoosh`_ to index |repos| and provide full-text search. |RCE| also provides support for `Elasticsearch`_ as a backend for scalable @@ -46,7 +46,7 @@ Configure the ``.rhoderc`` File ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |RCT| uses the :file:`/home/{user}/.rhoderc` file for connection details -to |RCM| instances. If this file is not automatically created, +to |RCE| instances. If this file is not automatically created, you can configure it using the following example. You need to configure the details for each instance you want to index. @@ -62,7 +62,7 @@ details for each instance you want to in - VERSION: 1.5.0 - URL: http://127.0.0.1:10000 -To get your API Token, on the |RCM| interface go to +To get your API Token, on the |RCE| interface go to :menuselection:`username --> My Account --> Auth tokens` .. code-block:: ini diff --git a/docs/admin/public-access.rst b/docs/admin/public-access.rst --- a/docs/admin/public-access.rst +++ b/docs/admin/public-access.rst @@ -3,7 +3,7 @@ Public Access ------------- -By default |RCM| allows users to read all **public** |repos|. User +By default |RCE| allows users to read all **public** |repos|. User permissions and |repo| access can be configured explicitly, and those permissions will override any default settings. The default settings can be found under the following section: diff --git a/docs/admin/repo-extra-fields.rst b/docs/admin/repo-extra-fields.rst --- a/docs/admin/repo-extra-fields.rst +++ b/docs/admin/repo-extra-fields.rst @@ -7,7 +7,7 @@ Extra fields attached to a |repo| allow each repository. This allows storing custom data per-repository. It can be used in :ref:`integrations-webhook` or in |RCX|. -To install and read more about |RCX|, see the :ref:`install-rcx` section. +To read more about |RCX|, see the :ref:`integrations-rcextensions` section. Enabling Extra Fields diff --git a/docs/admin/repo-hooks.rst b/docs/admin/repo-hooks.rst --- a/docs/admin/repo-hooks.rst +++ b/docs/admin/repo-hooks.rst @@ -46,11 +46,6 @@ This is the complete list of |repos| hoo Using Repository Hooks ---------------------- -To use these hooks you need to install |RCX|. For more information, see the -:ref:`install-rcx` section. +To use these hooks you need to setup |RCX|. For more information, see the +:ref:`integrations-rcextensions` section. -Creating Extensions -------------------- - -To create your own extensions using these hooks, see the :ref:`dev-plug` -section. diff --git a/docs/admin/setting-default-permissions.rst b/docs/admin/setting-default-permissions.rst --- a/docs/admin/setting-default-permissions.rst +++ b/docs/admin/setting-default-permissions.rst @@ -3,7 +3,7 @@ Setting Default Permissions --------------------------- -Default permissions allow you to configure |RCM| so that when a new |repo|, user group, +Default permissions allow you to configure |RCE| so that when a new |repo|, user group, or user is created their permissions are already defined. To set default permissions you need administrator privileges. See the following sections for setting up your permissions system: @@ -19,7 +19,7 @@ Setting User defaults To set default user permissions, use the following steps. -1. From the |RCM| interface, select :menuselection:`Admin --> Permissions` +1. From the |RCE| interface, select :menuselection:`Admin --> Permissions` 2. Select the :guilabel:`Global` tab from the left-hand menu. The permissions set on this screen apply to users and user-groups across the whole instance. 3. Save your changes @@ -31,7 +31,7 @@ Setting User Group defaults To set default user group permissions, use the following steps. -1. From the |RCM| interface, select :menuselection:`Admin --> User groups` +1. From the |RCE| interface, select :menuselection:`Admin --> User groups` 2. Select :guilabel:`Permissions`, and configure the default user permissions. All users will get these permissions unless individually set. @@ -48,7 +48,7 @@ Setting Repository defaults To set default |repo| permissions, use the following steps. -1. From the |RCM| interface, select :menuselection:`Admin --> Permissions` +1. From the |RCE| interface, select :menuselection:`Admin --> Permissions` 2. Select the :guilabel:`Object` tab from the left-hand menu and set the |perm| permissions 3. Save your changes @@ -60,7 +60,7 @@ Setting Repository Group defaults To set default Repository Group permissions, use the following steps. -1. From the |RCM| interface, select :menuselection:`Admin --> Repository Groups` +1. From the |RCE| interface, select :menuselection:`Admin --> Repository Groups` 2. Select :guilabel:`Edit` beside the |repo| group you wish to configure 3. On the left-hand pane select :guilabel:`Permissions` 4. Set the default permissions for all |repos| created in this group diff --git a/docs/admin/setting-repo-perms.rst b/docs/admin/setting-repo-perms.rst --- a/docs/admin/setting-repo-perms.rst +++ b/docs/admin/setting-repo-perms.rst @@ -3,12 +3,12 @@ Repository Administration ========================= -Repository permissions in |RCM| can be managed in a number of different ways. +Repository permissions in |RCE| can be managed in a number of different ways. This overview should give you an insight into how you could adopt particular settings for your needs: * Global |repo| permissions: This allows you to set the default permissions - for each new |repo| created within |RCM|, see :ref:`repo-default-ref`. All + for each new |repo| created within |RCE|, see :ref:`repo-default-ref`. All |repos| created will inherit these permissions unless explicitly configured. * Individual |repo| permissions: To set individual |repo| permissions, see :ref:`set-repo-perms`. diff --git a/docs/admin/svn-http.rst b/docs/admin/svn-http.rst --- a/docs/admin/svn-http.rst +++ b/docs/admin/svn-http.rst @@ -18,13 +18,14 @@ Prerequisites .. tip:: We recommend using Wandisco repositories which provide latest SVN versions - for most platforms. + for most platforms. If you skip this version you'll have to ensure the Client version + is compatible with installed SVN version which might differ depending on the operating system. Here is an example how to add the Wandisco repositories for Ubuntu. .. code-block:: bash - $ sudo sh -c 'echo "deb http://opensource.wandisco.com/ubuntu `lsb_release -cs` svn19" >> /etc/apt/sources.list.d/subversion19.list' - $ sudo wget -q http://opensource.wandisco.com/wandisco-debian.gpg -O- | sudo apt-key add - + $ sudo sh -c 'echo "deb http://opensource.wandisco.com/ubuntu `lsb_release -cs` svn110" >> /etc/apt/sources.list.d/subversion110.list' + $ sudo wget -q http://opensource.wandisco.com/wandisco-debian-new.gpg -O- | sudo apt-key add - $ sudo apt-get update Here is an example how to add the Wandisco repositories for Centos/Redhat. Using @@ -46,7 +47,7 @@ Example installation of required compone .. code-block:: bash $ sudo apt-get install apache2 - $ sudo apt-get install libapache2-mod-svn + $ sudo apt-get install libapache2-svn Once installed you need to enable ``dav_svn`` on Ubuntu: @@ -76,6 +77,12 @@ Once installed you need to enable ``dav_ LoadModule headers_module modules/mod_headers.so LoadModule authn_anon_module modules/mod_authn_anon.so +.. tip:: + + To check the installed mod_dav_svn module version, you can use such command. + + `strings /usr/lib/apache2/modules/mod_dav_svn.so | grep 'Powered by'` + Configuring Apache Setup ======================== diff --git a/docs/admin/system-overview.rst b/docs/admin/system-overview.rst --- a/docs/admin/system-overview.rst +++ b/docs/admin/system-overview.rst @@ -59,7 +59,7 @@ Supported Browsers System Requirements ------------------- -|RCM| performs best on machines with ultra-fast hard disks. Generally disk +|RCE| performs best on machines with ultra-fast hard disks. Generally disk performance is more important than CPU performance. In a corporate production environment handling 1000s of users and |repos| you should deploy on a 12+ core 64GB RAM server. In short, the more RAM the better. @@ -68,7 +68,7 @@ core 64GB RAM server. In short, the more For example: - for team of 1 - 5 active users you can run on 1GB RAM machine with 1CPU - - above 250 active users, |RCM| needs at least 8GB of memory. + - above 250 active users, |RCE| needs at least 8GB of memory. Number of CPUs is less important, but recommended to have at least 2-3 CPUs @@ -114,7 +114,7 @@ Connection Methods * HTTPS * SSH -* |RCM| API +* |RCE| API Internationalization Support ---------------------------- diff --git a/docs/admin/tuning-scale-horizontally-cluster.rst b/docs/admin/tuning-scale-horizontally-cluster.rst --- a/docs/admin/tuning-scale-horizontally-cluster.rst +++ b/docs/admin/tuning-scale-horizontally-cluster.rst @@ -146,7 +146,7 @@ persistent sessions across nodes. Please .. code-block:: ini - # use an unique generated long string + # use a unique generated long string beaker.session.secret = 70e116cae2274656ba7265fd860aebbd 3) Configure stored cached/archive cache to our shared NFS `rc-node-1` diff --git a/docs/admin/user-admin.rst b/docs/admin/user-admin.rst --- a/docs/admin/user-admin.rst +++ b/docs/admin/user-admin.rst @@ -3,7 +3,7 @@ User Administration =================== -|RCM| enables you to define permissions for the following entities within the +|RCE| enables you to define permissions for the following entities within the system; **users**, **user groups**, **repositories**, **repository groups**. Within each one of these entities you can set default settings, diff --git a/docs/admin/vcs-server.rst b/docs/admin/vcs-server.rst --- a/docs/admin/vcs-server.rst +++ b/docs/admin/vcs-server.rst @@ -3,13 +3,13 @@ VCS Server Management --------------------- -The VCS Server handles |RCM| backend functionality. You need to configure -a VCS Server to run with a |RCM| instance. If you do not, you will be missing -the connection between |RCM| and its |repos|. This will cause error messages +The VCS Server handles |RCE| backend functionality. You need to configure +a VCS Server to run with a |RCE| instance. If you do not, you will be missing +the connection between |RCE| and its |repos|. This will cause error messages on the web interface. You can run your setup in the following configurations, currently the best performance is one of following: -* One VCS Server per |RCM| instance. +* One VCS Server per |RCE| instance. * One VCS Server handling multiple instances. .. important:: @@ -49,7 +49,7 @@ To configure a |RCE| instance to use a V |RCE| VCS Server Options ^^^^^^^^^^^^^^^^^^^^^^^^ -The following list shows the available options on the |RCM| side of the +The following list shows the available options on the |RCE| side of the connection to the VCS Server. The settings are configured per instance in the :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file. @@ -75,7 +75,7 @@ instance in the \vcs.server Set the host, either hostname or IP Address, and port of the VCS server - you wish to run with your |RCM| instance. + you wish to run with your |RCE| instance. .. code-block:: ini diff --git a/docs/api/api.rst b/docs/api/api.rst --- a/docs/api/api.rst +++ b/docs/api/api.rst @@ -22,12 +22,12 @@ API access can also be turned on for eac decorated with a `@LoginRequired` decorator. To enable API access, change the standard login decorator to `@LoginRequired(api_access=True)`. -From |RCM| version 1.7.0 you can configure a white list +From |RCE| version 1.7.0 you can configure a white list of views that have API access enabled by default. To enable these, -edit the |RCM| configuration ``.ini`` file. The default location is: +edit the |RCE| configuration ``.ini`` file. The default location is: -* |RCM| Pre-2.2.7 :file:`root/rhodecode/data/production.ini` -* |RCM| 3.0 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` +* |RCE| Pre-2.2.7 :file:`root/rhodecode/data/production.ini` +* |RCE| 3.0 :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` To configure the white list, edit this section of the file. In this configuration example, API access is granted to the patch/diff raw file and diff --git a/docs/auth/auth-crowd.rst b/docs/auth/auth-crowd.rst --- a/docs/auth/auth-crowd.rst +++ b/docs/auth/auth-crowd.rst @@ -5,8 +5,8 @@ Crowd To enable Crowd authentication, use the following steps: -1. From the |RCM| interface, go to :menuselection:`Admin --> Authentication` -2. Enable the ``rhodecode.lib.auth_modules.auth_crowd`` library and select +1. From the |RCE| interface, go to :menuselection:`Admin --> Authentication` +2. Activate the ``rhodecode.lib.auth_modules.auth_crowd`` library and select :guilabel:`Save` 3. On the Crowd plugin settings section, do the following: diff --git a/docs/auth/auth-ldap-groups.rst b/docs/auth/auth-ldap-groups.rst --- a/docs/auth/auth-ldap-groups.rst +++ b/docs/auth/auth-ldap-groups.rst @@ -3,25 +3,24 @@ LDAP/AD With User Groups Sync ----------------------------- -|RCM| supports LDAP (Lightweight Directory Access Protocol) or +**This plugin is available only in EE Edition.** + +|RCE| supports LDAP (Lightweight Directory Access Protocol) or AD (active Directory) authentication. -All LDAP versions are supported, with the following |RCM| plugins managing each: - -* For LDAP/AD with user group sync use ``LDAP + User Groups (egg:rhodecode-enterprise-ee#ldap_group)`` +All LDAP versions are currently supported. RhodeCode reads all data defined from plugin and creates corresponding accounts on local database after receiving data from LDAP. This is done on every user log-in including operations like pushing/pulling/checkout. In addition group membership is read from LDAP and following operations are done: -- automatic addition of user to |RCM| user group -- automatic removal of user from any other |RCM| user groups not specified in LDAP. +- automatic addition of user to |RCE| user group +- automatic removal of user from any other |RCE| user groups not specified in LDAP. The removal is done *only* on groups that are marked to be synced from ldap. This setting can be changed in advanced settings on user groups -- automatic creation of user groups if they aren't yet existing in |RCM| +- automatic creation of user groups if they aren't yet existing in |RCE| - marking user as super-admins if he is a member of any admin group defined in plugin settings -This plugin is available only in EE Edition. .. important:: @@ -39,11 +38,12 @@ LDAP Configuration Steps To configure |LDAP|, use the following steps: -1. From the |RCM| interface, select +1. From the |RCE| interface, select :menuselection:`Admin --> Authentication` -2. Enable the ldap+ groups plugin and select :guilabel:`Save` -3. Select the :guilabel:`Enabled` check box in the plugin configuration section -4. Add the required LDAP information and :guilabel:`Save`, for more details, +2. Activate the `LDAP + User Groups` plugin and select :guilabel:`Save` +3. Go to newly available menu option called `LDAP + User Groups` on the left side. +4. Check the `enabled` check box in the plugin configuration section, + and fill in the required LDAP information and :guilabel:`Save`, for more details, see :ref:`config-ldap-groups-examples` For a more detailed description of LDAP objects, see :ref:`ldap-gloss-ref`: @@ -52,59 +52,107 @@ For a more detailed description of LDAP Example LDAP configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: bash + +Below is example setup that can be used with Active Directory and LDAP server with groups sync:: + + *option*: `enabled` => `True` + # Enable or disable this authentication plugin. + + *option*: `cache_ttl` => `360` + # Amount of seconds to cache the authentication and permissions check response call for this plugin. + # Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). + + *option*: `host` => `192.168.245.143,192.168.1.240` + # Host[s] of the LDAP Server + # (e.g., 192.168.2.154, or ldap-server.domain.com. + # Multiple servers can be specified using commas + + *option*: `port` => `389` + # Custom port that the LDAP server is listening on. Default value is: 389, use 689 for LDAPS(SSL) + + *option*: `timeout` => `300` + # Timeout for LDAP connection + + *option*: `dn_user` => `Administrator@rhodecode.com` + # Optional user DN/account to connect to LDAP if authentication is required. + # e.g., cn=admin,dc=mydomain,dc=com, or uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com + + *option*: `dn_pass` => `SomeSecret` + # Password to authenticate for given user DN. + + *option*: `tls_kind` => `PLAIN` + # TLS Type + + *option*: `tls_reqcert` => `NEVER` + # Require Cert over TLS?. Self-signed and custom certificates can be used when + # `RhodeCode Certificate` found in admin > settings > system info page is extended. + + *option*: `tls_cert_file` => `` + # This specifies the PEM-format file path containing certificates for use in TLS connection. + # If not specified `TLS Cert dir` will be used + + *option*: `tls_cert_dir` => `/etc/openldap/cacerts` + # This specifies the path of a directory that contains individual CA certificates in separate files. + + *option*: `base_dn` => `dc=rhodecode,dc=com` + # Base DN to search. Dynamic bind is supported. Add `$login` marker in it to be replaced with current user credentials + # (e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com) + + *option*: `user_search_base` => `ou=RC-Users` + # User search base will extend the Base DN + # (e.g., ou=Users will result in ou=Users,dc=mydomain,dc=com root DN) - # Auth Cache TTL, Defines the caching for authentication to offload LDAP server. - # This means that cache result will be saved for 3600 before contacting LDAP server to verify the user access - 3600 - # Host, comma seperated format is optionally possible to specify more than 1 server - https://ldap1.server.com/ldap-admin/,https://ldap2.server.com/ldap-admin/ - # Default LDAP Port, use 689 for LDAPS - 389 - # Account, used for SimpleBind if LDAP server requires an authentication - e.g admin@server.com - # Password used for simple bind - ldap-user-password - # LDAP connection security - LDAPS - # Certificate checks level - DEMAND - # Base DN - cn=Rufus Magillacuddy,ou=users,dc=rhodecode,dc=com - # User Search Base - ou=groups,ou=users - # LDAP search filter to narrow the results - (objectClass=person) - # LDAP search scope - SUBTREE - # Login attribute - sAMAccountName - # First Name Attribute to read - givenName - # Last Name Attribute to read - sn - # Email Attribute to read email address from - mail - # group extraction method - rfc2307bis - # Group search base - ou=RC-Groups - # Group Name Attribute, field to read the group name from - sAMAAccountName - # User Member of Attribute, field in which groups are stored - memberOf - # LDAP Group Search Filter, allows narrowing the results + *option*: `user_search_filter` => `` + # Filter to narrow results + # (e.g., (&(objectCategory=Person)(objectClass=user)), or + # (memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com))) + + *option*: `search_scope` => `SUBTREE` + # How deep to search LDAP. If unsure set to SUBTREE + + *option*: `attr_login` => `sAMAccountName` + # LDAP Attribute to map to user name (e.g., uid, or sAMAccountName) + + *option*: `attr_email` => `mail` + # LDAP Attribute to map to email address (e.g., mail). + # Emails are a crucial part of RhodeCode. + # If possible add a valid email attribute to ldap users. + + *option*: `attr_firstname` => `givenName` + # LDAP Attribute to map to first name (e.g., givenName) + + *option*: `attr_lastname` => `sn` + # LDAP Attribute to map to last name (e.g., sn) + + *option*: `group_extraction_type` => `rfc2307bis` + # With rfc2307, group members are listed by name in the memberUid attribute + # With rfc2307bis (Microsoft AD compatible) group members are listed by DN and stored in the member attribute - # Admin Groups. Comma separated list of groups. If user is member of - # any of those he will be marked as super-admin in RhodeCode - admins, management + *option*: `group_search_base` => `ou=RC-Groups` + # Group search base will extend the Base DN (e.g. ou=Groups will result in ou=Groups,dc=mydomain,dc=com) + *option*: `group_name_attr` => `sAMAccountName` + # LDAP Attribute to map to group name (e.g., cn, or sAMAccountName) + + *option*: `user_member_of` => `memberOf` + # Users Attribute used to fetch the group membership. + # Use if users have stored group membership inside their attributes + # (e.g., memberOf, or userMemberOf) -Below is example setup that can be used with Active Directory and ldap groups. + *option*: `group_search_filter` => `` + # Filter to narrow results (e.g., (&(objectCategory=Group)(objectClass=group)), etc) + + *option*: `group_member_of` => `memberOf` + # LDAP Attribute used to resolve the parent group (e.g., memberOf) -.. image:: ../images/ldap-groups-example.png - :alt: LDAP/AD setup example - :scale: 50 % + *option*: `admin_groups` => `Admins,Management` + # A comma separated list of group names that identify users as RhodeCode Administrators (e.g., admins) + + *option*: `admin_groups_sync` => `full` + # Way to sync Admin groups. + # Full means admin flag is set to on or off according to membership in administrator group defined above. + # On-only means the flag is only set to on, and not turned off once user is no longer a member + .. toctree:: diff --git a/docs/auth/auth-ldap.rst b/docs/auth/auth-ldap.rst --- a/docs/auth/auth-ldap.rst +++ b/docs/auth/auth-ldap.rst @@ -3,11 +3,9 @@ LDAP/AD ------- -|RCM| supports LDAP (Lightweight Directory Access Protocol) or +|RCE| supports LDAP (Lightweight Directory Access Protocol) or AD (active Directory) authentication. -All LDAP versions are supported, with the following |RCM| plugins managing each: - -* For LDAP or Active Directory use ``LDAP (egg:rhodecode-enterprise-ce#ldap)`` +All LDAP versions are currently supported. RhodeCode reads all data defined from plugin and creates corresponding accounts on local database after receiving data from LDAP. This is done on @@ -30,11 +28,12 @@ LDAP Configuration Steps To configure |LDAP|, use the following steps: -1. From the |RCM| interface, select +1. From the |RCE| interface, select :menuselection:`Admin --> Authentication` -2. Enable the ldap plugin and select :guilabel:`Save` -3. Select the :guilabel:`Enabled` check box in the plugin configuration section -4. Add the required LDAP information and :guilabel:`Save`, for more details, +2. Activate the `LDAP` plugin and select :guilabel:`Save` +3. Go to newly available menu option called `LDAP` on the left side. +4. Check the `enabled` check box in the plugin configuration section, + and fill in the required LDAP information and :guilabel:`Save`, for more details, see :ref:`config-ldap-examples` For a more detailed description of LDAP objects, see :ref:`ldap-gloss-ref`: @@ -43,44 +42,73 @@ For a more detailed description of LDAP Example LDAP configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: bash + +Below is example setup that can be used with Active Directory/LDAP server:: + + *option*: `enabled` => `True` + # Enable or disable this authentication plugin. + + *option*: `cache_ttl` => `360` + # Amount of seconds to cache the authentication and permissions check response call for this plugin. + # Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). + + *option*: `host` => `192.168.245.143,192.168.1.240` + # Host[s] of the LDAP Server + # (e.g., 192.168.2.154, or ldap-server.domain.com. + # Multiple servers can be specified using commas + + *option*: `port` => `389` + # Custom port that the LDAP server is listening on. Default value is: 389, use 689 for LDAPS(SSL) + + *option*: `timeout` => `300` + # Timeout for LDAP connection + + *option*: `dn_user` => `Administrator@rhodecode.com` + # Optional user DN/account to connect to LDAP if authentication is required. + # e.g., cn=admin,dc=mydomain,dc=com, or uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com + + *option*: `dn_pass` => `SomeSecret` + # Password to authenticate for given user DN. + + *option*: `tls_kind` => `PLAIN` + # TLS Type - # Auth Cache TTL, Defines the caching for authentication to offload LDAP server. - # This means that cache result will be saved for 3600 before contacting LDAP server to verify the user access - 3600 - # Host, comma seperated format is optionally possible to specify more than 1 server - https://ldap1.server.com/ldap-admin/,https://ldap2.server.com/ldap-admin/ - # Default LDAP Port, use 689 for LDAPS - 389 - # Account, used for SimpleBind if LDAP server requires an authentication - e.g admin@server.com - # Password used for simple bind - ldap-user-password - # LDAP connection security - LDAPS - # Certificate checks level - DEMAND - # Base DN - cn=Rufus Magillacuddy,ou=users,dc=rhodecode,dc=com - # LDAP search filter to narrow the results - (objectClass=person) - # LDAP search scope - SUBTREE - # Login attribute - sAMAccountName - # First Name Attribute to read - givenName - # Last Name Attribute to read - sn - # Email Attribute to read email address from - mail + *option*: `tls_reqcert` => `NEVER` + # Require Cert over TLS?. Self-signed and custom certificates can be used when + # `RhodeCode Certificate` found in admin > settings > system info page is extended. + + *option*: `tls_cert_file` => `` + # This specifies the PEM-format file path containing certificates for use in TLS connection. + # If not specified `TLS Cert dir` will be used + + *option*: `tls_cert_dir` => `/etc/openldap/cacerts` + # This specifies the path of a directory that contains individual CA certificates in separate files. + + *option*: `base_dn` => `cn=Rufus Magillacuddy,ou=users,dc=rhodecode,dc=com` + # Base DN to search. Dynamic bind is supported. Add `$login` marker in it to be replaced with current user credentials + # (e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com) + *option*: `filter` => `(objectClass=person)` + # Filter to narrow results + # (e.g., (&(objectCategory=Person)(objectClass=user)), or + # (memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com))) -Below is example setup that can be used with Active Directory/LDAP server. + *option*: `search_scope` => `SUBTREE` + # How deep to search LDAP. If unsure set to SUBTREE + + *option*: `attr_login` => `sAMAccountName` + # LDAP Attribute to map to user name (e.g., uid, or sAMAccountName) -.. image:: ../images/ldap-example.png - :alt: LDAP/AD setup example - :scale: 50 % + *option*: `attr_email` => `mail` + # LDAP Attribute to map to email address (e.g., mail). + # Emails are a crucial part of RhodeCode. + # If possible add a valid email attribute to ldap users. + + *option*: `attr_firstname` => `givenName` + # LDAP Attribute to map to first name (e.g., givenName) + + *option*: `attr_lastname` => `sn` + # LDAP Attribute to map to last name (e.g., sn) .. toctree:: diff --git a/docs/auth/auth-pam.rst b/docs/auth/auth-pam.rst --- a/docs/auth/auth-pam.rst +++ b/docs/auth/auth-pam.rst @@ -5,8 +5,8 @@ PAM To enable PAM authentication, use the following steps: -1. From the |RCM| interface, go to :menuselection:`Admin --> Authentication` -2. Enable the ``rhodecode.lib.auth_modules.auth_pam`` library and select save +1. From the |RCE| interface, go to :menuselection:`Admin --> Authentication` +2. Activate the ``rhodecode.lib.auth_modules.auth_pam`` library and select save 3. On the PAM plugin settings section, do the following: * Check the :guilabel:`Enable` checkbox diff --git a/docs/auth/auth-saml-duosecurity.rst b/docs/auth/auth-saml-duosecurity.rst new file mode 100644 --- /dev/null +++ b/docs/auth/auth-saml-duosecurity.rst @@ -0,0 +1,105 @@ +.. _config-saml-duosecurity-ref: + + +SAML 2.0 with Duo Security +-------------------------- + +**This plugin is available only in EE Edition.** + +|RCE| supports SAML 2.0 Authentication with Duo Security provider. This allows +users to log-in to RhodeCode via SSO mechanism of external identity provider +such as Duo. The login can be triggered either by the external IDP, or internally +by clicking specific authentication button on the log-in page. + + +Configuration steps +^^^^^^^^^^^^^^^^^^^ + +To configure Duo Security SAML authentication, use the following steps: + +1. From the |RCE| interface, select + :menuselection:`Admin --> Authentication` +2. Activate the `Duo Security` plugin and select :guilabel:`Save` +3. Go to newly available menu option called `Duo Security` on the left side. +4. Check the `enabled` check box in the plugin configuration section, + and fill in the required SAML information and :guilabel:`Save`, for more details, + see :ref:`config-saml-duosecurity` + + +.. _config-saml-duosecurity: + + +Example SAML Duo Security configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Example configuration for SAML 2.0 with Duo Security provider:: + + *option*: `enabled` => `True` + # Enable or disable this authentication plugin. + + *option*: `cache_ttl` => `0` + # Amount of seconds to cache the authentication and permissions check response call for this plugin. + # Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). + + *option*: `debug` => `True` + # Enable or disable debug mode that shows SAML errors in the RhodeCode logs. + + *option*: `entity_id` => `http://rc-app.com/dag/saml2/idp/metadata.php` + # Identity Provider entity/metadata URI. + # E.g. https://duo-gateway.com/dag/saml2/idp/metadata.php + + *option*: `sso_service_url` => `http://rc-app.com/dag/saml2/idp/SSOService.php?spentityid=http://rc.local.pl/_admin/auth/duosecurity/saml-metadata` + # SSO (SingleSignOn) endpoint URL of the IdP. This can be used to initialize login + # E.g. https://duo-gateway.com/dag/saml2/idp/SSOService.php?spentityid= + + *option*: `slo_service_url` => `http://rc-app.com/dag/saml2/idp/SingleLogoutService.php?ReturnTo=http://rc-app.com/dag/module.php/duosecurity/logout.php` + # SLO (SingleLogout) endpoint URL of the IdP. + # E.g. https://duo-gateway.com/dag/saml2/idp/SingleLogoutService.php?ReturnTo=http://duo-gateway.com/_admin/saml/sign-out-endpoint + + *option*: `x509cert` => `` + # Identity provider public x509 certificate. It will be converted to single-line format without headers + + *option*: `name_id_format` => `sha-1` + # The format that specifies how the NameID is sent to the service provider. + + *option*: `signature_algo` => `sha-256` + # Type of Algorithm to use for verification of SAML signature on Identity provider side + + *option*: `digest_algo` => `sha-256` + # Type of Algorithm to use for verification of SAML digest on Identity provider side + + *option*: `cert_dir` => `/etc/saml/` + # Optional directory to store service provider certificate and private keys. + # Expected certs for the SP should be stored in this folder as: + # * sp.key Private Key + # * sp.crt Public cert + # * sp_new.crt Future Public cert + # + # Also you can use other cert to sign the metadata of the SP using the: + # * metadata.key + # * metadata.crt + + *option*: `user_id_attribute` => `PersonImmutableID` + # User ID Attribute name. This defines which attribute in SAML response will be used to link accounts via unique id. + # Ensure this is returned from DuoSecurity for example via duo_username + + *option*: `username_attribute` => `User.username` + # Username Attribute name. This defines which attribute in SAML response will map to an username. + + *option*: `email_attribute` => `User.email` + # Email Attribute name. This defines which attribute in SAML response will map to an email address. + + +Below is example setup from DUO Administration page that can be used with above config. + +.. image:: ../images/saml-duosecurity-service-provider-example.png + :alt: DUO Security SAML setup example + :scale: 50 % + + +Below is an example attribute mapping set for IDP provider required by the above config. + + +.. image:: ../images/saml-duosecurity-attributes-example.png + :alt: DUO Security SAML setup example + :scale: 50 % \ No newline at end of file diff --git a/docs/auth/auth-saml-generic.rst b/docs/auth/auth-saml-generic.rst new file mode 100644 --- /dev/null +++ b/docs/auth/auth-saml-generic.rst @@ -0,0 +1,18 @@ +.. _config-saml-generic-ref: + + +SAML 2.0 Authentication +----------------------- + + +**This plugin is available only in EE Edition.** + +RhodeCode Supports standard SAML 2.0 SSO for the web-application part. + +Please check for reference two example providers: + +.. toctree:: + + auth-saml-duosecurity + auth-saml-onelogin + diff --git a/docs/auth/auth-saml-onelogin.rst b/docs/auth/auth-saml-onelogin.rst new file mode 100644 --- /dev/null +++ b/docs/auth/auth-saml-onelogin.rst @@ -0,0 +1,106 @@ +.. _config-saml-onelogin-ref: + + +SAML 2.0 with One Login +----------------------- + +**This plugin is available only in EE Edition.** + +|RCE| supports SAML 2.0 Authentication with OneLogin provider. This allows +users to log-in to RhodeCode via SSO mechanism of external identity provider +such as OneLogin. The login can be triggered either by the external IDP, or internally +by clicking specific authentication button on the log-in page. + + +Configuration steps +^^^^^^^^^^^^^^^^^^^ + +To configure OneLogin SAML authentication, use the following steps: + +1. From the |RCE| interface, select + :menuselection:`Admin --> Authentication` +2. Activate the `OneLogin` plugin and select :guilabel:`Save` +3. Go to newly available menu option called `OneLogin` on the left side. +4. Check the `enabled` check box in the plugin configuration section, + and fill in the required SAML information and :guilabel:`Save`, for more details, + see :ref:`config-saml-onelogin` + + +.. _config-saml-onelogin: + + +Example SAML OneLogin configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Example configuration for SAML 2.0 with OneLogin provider:: + + *option*: `enabled` => `True` + # Enable or disable this authentication plugin. + + *option*: `cache_ttl` => `0` + # Amount of seconds to cache the authentication and permissions check response call for this plugin. + # Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). + + *option*: `debug` => `True` + # Enable or disable debug mode that shows SAML errors in the RhodeCode logs. + + *option*: `entity_id` => `https://app.onelogin.com/saml/metadata/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` + # Identity Provider entity/metadata URI. + # E.g. https://app.onelogin.com/saml/metadata/ + + *option*: `sso_service_url` => `https://customer-domain.onelogin.com/trust/saml2/http-post/sso/xxxxxx` + # SSO (SingleSignOn) endpoint URL of the IdP. This can be used to initialize login + # E.g. https://app.onelogin.com/trust/saml2/http-post/sso/ + + *option*: `slo_service_url` => `https://customer-domain.onelogin.com/trust/saml2/http-redirect/slo/xxxxxx` + # SLO (SingleLogout) endpoint URL of the IdP. + # E.g. https://app.onelogin.com/trust/saml2/http-redirect/slo/ + + *option*: `x509cert` => `` + # Identity provider public x509 certificate. It will be converted to single-line format without headers + + *option*: `name_id_format` => `sha-1` + # The format that specifies how the NameID is sent to the service provider. + + *option*: `signature_algo` => `sha-256` + # Type of Algorithm to use for verification of SAML signature on Identity provider side + + *option*: `digest_algo` => `sha-256` + # Type of Algorithm to use for verification of SAML digest on Identity provider side + + *option*: `cert_dir` => `/etc/saml/` + # Optional directory to store service provider certificate and private keys. + # Expected certs for the SP should be stored in this folder as: + # * sp.key Private Key + # * sp.crt Public cert + # * sp_new.crt Future Public cert + # + # Also you can use other cert to sign the metadata of the SP using the: + # * metadata.key + # * metadata.crt + + *option*: `user_id_attribute` => `PersonImmutableID` + # User ID Attribute name. This defines which attribute in SAML response will be used to link accounts via unique id. + # Ensure this is returned from OneLogin for example via Internal ID + + *option*: `username_attribute` => `User.username` + # Username Attribute name. This defines which attribute in SAML response will map to an username. + + *option*: `email_attribute` => `User.email` + # Email Attribute name. This defines which attribute in SAML response will map to an email address. + + + +Below is example setup that can be used with OneLogin SAML authentication that can be used with above config.. + +.. image:: ../images/saml-onelogin-config-example.png + :alt: OneLogin SAML setup example + :scale: 50 % + + +Below is an example attribute mapping set for IDP provider required by the above config. + + +.. image:: ../images/saml-onelogin-attributes-example.png + :alt: OneLogin SAML setup example + :scale: 50 % \ No newline at end of file diff --git a/docs/auth/auth-token.rst b/docs/auth/auth-token.rst --- a/docs/auth/auth-token.rst +++ b/docs/auth/auth-token.rst @@ -3,7 +3,10 @@ Authentication Tokens --------------------- -|RCE| has 4 different kinds of authentication tokens. +|RCE| has 4 different kinds of authentication tokens. `API token`, `Feed tokens` work +without a need to enable any additional authentication. `VCS tokens` require dedicated +authentication plugin to be activated. `Web Interface tokens` are controlled by the +white_list configuration. * *API tokens*: API tokens can only be used to execute |RCE| API operations. You can store your API token and assign it to each instance in @@ -11,15 +14,7 @@ Authentication Tokens example in :ref:`indexing-ref` section for more details. * *Feed tokens*: The feed token can only be used to access the RSS feed. - Usually those are safe to store inside your RSS feed reader. - -* *VCS tokens*: You can use these to authenticate with |git|, |hg| and |svn| - operations instead of a password. They are designed to be used with - CI Servers or other third party tools that require |repo| access. - They are also a good replacement for SSH based access. - To use these tokens you need be enabled special authentication method on - |RCE|, as they are disabled by default. - See :ref:`enable-vcs-tokens`. + Usually those are safe to store inside your RSS feed reader. * *Web Interface tokens*: These token allows users to access the web interface of |RCE| without logging in. @@ -41,7 +36,16 @@ Authentication Tokens https://rhodecode.com/repo/archive/tip.zip?auth_token= # To show commit diff without logging into Web UI - https://rhodecode.com/repo/changeset-diff/?auth_token= + https://rhodecode.com/repo/raw-diff/?auth_token= + +* *VCS tokens*: You can use these to authenticate with |git|, |hg| and |svn| + operations instead of a password. They are designed to be used with + CI Servers or other third party tools that require |repo| access. + They are also a good replacement for SSH based access. + To use these tokens you need be enabled special authentication method on + |RCE|, as they are disabled by default. + See :ref:`enable-vcs-tokens`. + .. _enable-vcs-tokens: @@ -51,7 +55,7 @@ Enabling VCS Tokens To enable VCS Tokens, use the following steps: 1. Go to :menuselection:`Admin --> Authentication`. -2. Enable the ``rhodecode.lib.auth_modules.auth_token`` plugin. +2. Activate the ``rhodecode.lib.auth_modules.auth_token`` plugin. 3. Click :guilabel:`Save`. Authentication Token Tips @@ -67,7 +71,7 @@ Creating Tokens To create authentication tokens for an user, use the following steps: -1. From the |RCM| interface go to +1. From the |RCE| interface go to :menuselection:`Username --> My Account --> Auth tokens`. 2. Label and Add the tokens you wish to use with |RCE|. diff --git a/docs/auth/auth.rst b/docs/auth/auth.rst --- a/docs/auth/auth.rst +++ b/docs/auth/auth.rst @@ -4,29 +4,31 @@ Authentication Options ====================== |RCE| provides a built in authentication against its own database. This is -implemented using ``rhodecode.lib.auth_rhodecode`` plugin. This plugin is -enabled by default. +implemented using ``RhodeCode Internal`` plugin. This plugin is enabled by default. Additionally, |RCE| provides a Pluggable Authentication System. This gives the administrator greater control over how users authenticate with the system. .. important:: - You can disable the built in |RCM| authentication plugin - ``rhodecode.lib.auth_rhodecode`` and force all authentication to go + You can disable the built in |RCE| authentication plugin + ``RhodeCode Internal`` and force all authentication to go through your authentication plugin of choice e.g LDAP only. However, if you do this, and your external authentication tools fails, - you will be unable to access |RCM|. + accessing |RCE| will be blocked unless a fallback plugin is + enabled via :file: rhodecode.ini -|RCM| comes with the following user authentication management plugins: + +|RCE| comes with the following user authentication management plugins: .. toctree:: + auth-token auth-ldap auth-ldap-groups + auth-saml-generic + auth-saml-onelogin + auth-saml-duosecurity auth-crowd auth-pam - auth-token ssh-connection - - diff --git a/docs/auth/ldap-active-directory.rst b/docs/auth/ldap-active-directory.rst --- a/docs/auth/ldap-active-directory.rst +++ b/docs/auth/ldap-active-directory.rst @@ -3,27 +3,73 @@ Active Directory ---------------- -|RCM| can use Microsoft Active Directory for user authentication. This is +|RCE| can use Microsoft Active Directory for user authentication. This is done through an LDAP or LDAPS connection to Active Directory. Use the following example LDAP configuration setting to set your Active Directory -authentication. - -.. code-block:: ini - - # Set the Base DN - Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local - # Set the Active Directory SAM-Account-Name - Login Attribute = sAMAccountName - # Set the Active Directory user name - First Name Attribute = usernameame - # Set the Active Directory user surname - Last Name Attribute = user_surname - # Set the Active Directory user email - E-mail Attribute = userEmail +authentication:: -Below is example setup that can be used with Active Directory and ldap groups. + *option*: `enabled` => `True` + # Enable or disable this authentication plugin. + + *option*: `cache_ttl` => `360` + # Amount of seconds to cache the authentication and permissions check response call for this plugin. + # Useful for expensive calls like LDAP to improve the performance of the system (0 means disabled). + + *option*: `host` => `192.168.245.143,192.168.1.240` + # Host[s] of the LDAP Server + # (e.g., 192.168.2.154, or ldap-server.domain.com. + # Multiple servers can be specified using commas + + *option*: `port` => `389` + # Custom port that the LDAP server is listening on. Default value is: 389, use 689 for LDAPS(SSL) + + *option*: `timeout` => `300` + # Timeout for LDAP connection + + *option*: `dn_user` => `Administrator@rhodecode.com` + # Optional user DN/account to connect to LDAP if authentication is required. + # e.g., cn=admin,dc=mydomain,dc=com, or uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com + + *option*: `dn_pass` => `SomeSecret` + # Password to authenticate for given user DN. + + *option*: `tls_kind` => `PLAIN` + # TLS Type + + *option*: `tls_reqcert` => `NEVER` + # Require Cert over TLS?. Self-signed and custom certificates can be used when + # `RhodeCode Certificate` found in admin > settings > system info page is extended. -.. image:: ../images/ldap-groups-example.png - :alt: LDAP/AD setup example - :scale: 50 % \ No newline at end of file + *option*: `tls_cert_file` => `` + # This specifies the PEM-format file path containing certificates for use in TLS connection. + # If not specified `TLS Cert dir` will be used + + *option*: `tls_cert_dir` => `/etc/openldap/cacerts` + # This specifies the path of a directory that contains individual CA certificates in separate files. + + *option*: `base_dn` => `OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local` + # Base DN to search. Dynamic bind is supported. Add `$login` marker in it to be replaced with current user credentials + # (e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com) + + *option*: `filter` => `(objectClass=person)` + # Filter to narrow results + # (e.g., (&(objectCategory=Person)(objectClass=user)), or + # (memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com))) + + *option*: `search_scope` => `SUBTREE` + # How deep to search LDAP. If unsure set to SUBTREE + + *option*: `attr_login` => `sAMAccountName` + # LDAP Attribute to map to user name (e.g., uid, or sAMAccountName) + + *option*: `attr_email` => `userEmail` + # LDAP Attribute to map to email address (e.g., mail). + # Emails are a crucial part of RhodeCode. + # If possible add a valid email attribute to ldap users. + + *option*: `attr_firstname` => `user_firstname` + # LDAP Attribute to map to first name (e.g., givenName) + + *option*: `attr_lastname` => `user_surname` + # LDAP Attribute to map to last name (e.g., sn) diff --git a/docs/auth/ldap-authentication.rst b/docs/auth/ldap-authentication.rst --- a/docs/auth/ldap-authentication.rst +++ b/docs/auth/ldap-authentication.rst @@ -46,7 +46,7 @@ The following LDAP attributes are requir * The LDAP username or account used to connect to |RCE|. This will be added to the LDAP filter for locating the user object. * For example, if an LDAP filter is specified as `LDAPFILTER`, - the login attribute is specified as `uid`, and the user connects as + the login/username attribute is specified as `uid`, and the user connects as `jsmith`, then the LDAP Filter will be like the following example. .. code-block:: vim @@ -68,7 +68,7 @@ The following LDAP attributes are requir Optional settings ^^^^^^^^^^^^^^^^^ -The following are optional when enabling LDAP on |RCM| +The following are optional when enabling LDAP on |RCE| * An LDAP account is only required if the LDAP server does not allow anonymous browsing of records. @@ -104,10 +104,4 @@ The following are optional when enabling following directory: `/etc/openldap/cacerts` -Below is example setup that can be used with Active Directory and ldap groups. - -.. image:: ../images/ldap-groups-example.png - :alt: LDAP/AD setup example - :scale: 50 % - .. _RFC 2254: http://www.rfc-base.org/rfc-2254.html \ No newline at end of file diff --git a/docs/code-review/code-review.rst b/docs/code-review/code-review.rst --- a/docs/code-review/code-review.rst +++ b/docs/code-review/code-review.rst @@ -3,7 +3,7 @@ Code Review =========== -|RCM| provides two ways in which you can review code. You can review |prs| or +|RCE| provides two ways in which you can review code. You can review |prs| or commits. To better understand |prs|, see the :ref:`pull-requests-ref` and :ref:`collaborate-ref` sections. For more information about why code review matters, see these posts on the topic: diff --git a/docs/code-review/review-diffs.rst b/docs/code-review/review-diffs.rst --- a/docs/code-review/review-diffs.rst +++ b/docs/code-review/review-diffs.rst @@ -16,7 +16,7 @@ review purposes. Reviewing Changes ----------------- -|RCM| displays all code changes made with each commit. Removed content is +|RCE| displays all code changes made with each commit. Removed content is marked in red and new content in green. .. image:: ../images/plain-diff.png diff --git a/docs/collaboration/approve-pull-request.rst b/docs/collaboration/approve-pull-request.rst --- a/docs/collaboration/approve-pull-request.rst +++ b/docs/collaboration/approve-pull-request.rst @@ -10,7 +10,7 @@ 3. Leave a commit message that outlines 4. Set the review status to :guilabel:`Approved` 5. Select :guilabel:`Comment` -If you approve the |pr|, you will be able to merge automatically if |RCM| +If you approve the |pr|, you will be able to merge automatically if |RCE| detects that it can do so safely. You will see this message: :guilabel:`This pull request can be automatically merged.` diff --git a/docs/collaboration/collaboration.rst b/docs/collaboration/collaboration.rst --- a/docs/collaboration/collaboration.rst +++ b/docs/collaboration/collaboration.rst @@ -7,7 +7,7 @@ Collaboration Forking and branching does not work with |svn| |repos|. -Collaboration in |RCM| is accomplished through a combination of the following +Collaboration in |RCE| is accomplished through a combination of the following functions: .. only:: latex diff --git a/docs/collaboration/forks-branches.rst b/docs/collaboration/forks-branches.rst --- a/docs/collaboration/forks-branches.rst +++ b/docs/collaboration/forks-branches.rst @@ -55,7 +55,7 @@ on the web interface. To branch a |git| $ git commit -a -m "ghost script: initial file" $ git push -Once it is pushed to the |RCM| server, you can switch to the newly created +Once it is pushed to the |RCE| server, you can switch to the newly created branch using the following steps: 1. Select :menuselection:`Admin --> Repositories`. diff --git a/docs/collaboration/mention-reviewer-function.rst b/docs/collaboration/mention-reviewer-function.rst --- a/docs/collaboration/mention-reviewer-function.rst +++ b/docs/collaboration/mention-reviewer-function.rst @@ -4,7 +4,7 @@ Using Notifications ------------------- To notify users of items that require their attention you can use the mention -function. The mention function allows you to use ``@username`` within |RCM|. +function. The mention function allows you to use ``@username`` within |RCE|. The notification function can be used within the following items to highlight their need for attention: diff --git a/docs/collaboration/merge-pull-request.rst b/docs/collaboration/merge-pull-request.rst --- a/docs/collaboration/merge-pull-request.rst +++ b/docs/collaboration/merge-pull-request.rst @@ -3,7 +3,7 @@ Merge a |pr| ------------ -|RCM| can detect if it can automatically merge the changes in a |pr|. If it +|RCE| can detect if it can automatically merge the changes in a |pr|. If it can, you will see the following message: :guilabel:`This pull request can be automatically merged.` To merge, click the big blue button! To enable this feature, see :ref:`server-side-merge`. @@ -21,7 +21,7 @@ messages: Manual Merge a |PR| ^^^^^^^^^^^^^^^^^^^ -If |RCM| cannot safely merge the changes in a |pr|, +If |RCE| cannot safely merge the changes in a |pr|, usually due to conflicts, you need to manually merge the changes on the command line. You can see more information for each |repo| type at the following links: diff --git a/docs/collaboration/notifications-overview.rst b/docs/collaboration/notifications-overview.rst --- a/docs/collaboration/notifications-overview.rst +++ b/docs/collaboration/notifications-overview.rst @@ -1,7 +1,7 @@ Notifications Overview ---------------------- -|RCM| has an integrated notification system which alerts users to requests +|RCE| has an integrated notification system which alerts users to requests that they have received. Notifications can occur for the following reasons: * Pull request reviews diff --git a/docs/collaboration/pull-req-mgmt.rst b/docs/collaboration/pull-req-mgmt.rst --- a/docs/collaboration/pull-req-mgmt.rst +++ b/docs/collaboration/pull-req-mgmt.rst @@ -3,7 +3,7 @@ Pull request management .. only:: html - There are two ways of tracking |prs| within |RCM|. + There are two ways of tracking |prs| within |RCE|. 1. :ref:`prs-your-review` 2. :ref:`prs-per-repo` @@ -15,7 +15,7 @@ Pull requests for your review To view pull requests for your review, use the following steps: -1. From the |RCM| interface, Select +1. From the |RCE| interface, Select :menuselection:`username --> Notifications` 2. Select :guilabel:`Pull Requests` diff --git a/docs/collaboration/review-pull-request.rst b/docs/collaboration/review-pull-request.rst --- a/docs/collaboration/review-pull-request.rst +++ b/docs/collaboration/review-pull-request.rst @@ -20,7 +20,7 @@ 3. Set the review status from one of the 4. Select Comment When the |pr| is approved by all reviewers you will be able to merge -automatically if |RCM| detects that it can do so safely. You will see this +automatically if |RCE| detects that it can do so safely. You will see this message: `This pull request can be automatically merged.` If rejected, you can fix the issues raised during review and then update the diff --git a/docs/collaboration/supported-workflows.rst b/docs/collaboration/supported-workflows.rst --- a/docs/collaboration/supported-workflows.rst +++ b/docs/collaboration/supported-workflows.rst @@ -1,7 +1,7 @@ Supported Workflows ------------------- -|RCM| can be used to develop using a variety of different workflows. +|RCE| can be used to develop using a variety of different workflows. * Centralized, using |svn|, |git|, or |hg| |repos| * Feature-Branch, using |git| or |hg| |repos| diff --git a/docs/common.py b/docs/common.py --- a/docs/common.py +++ b/docs/common.py @@ -20,10 +20,7 @@ rst_epilog = ''' .. |psf| replace:: Python Software Foundation .. |repo| replace:: repository .. |repos| replace:: repositories -.. |RCI| replace:: RhodeCode Control .. |RCC| replace:: RhodeCode Control -.. |RCV| replace:: RhodeCode Enterprise -.. |RCM| replace:: RhodeCode Enterprise .. |RCE| replace:: RhodeCode Enterprise .. |RCCE| replace:: RhodeCode Community .. |RCEE| replace:: RhodeCode Enterprise @@ -31,6 +28,5 @@ rst_epilog = ''' .. |RCT| replace:: RhodeCode Tools .. |RCEBOLD| replace:: **RhodeCode Enterprise** .. |RCEITALICS| replace:: `RhodeCode Enterprise` -.. |RC| replace:: RhodeCode .. |RNS| replace:: Release Notes ''' diff --git a/docs/extensions/extensions-hooks.rst b/docs/extensions/extensions-hooks.rst --- a/docs/extensions/extensions-hooks.rst +++ b/docs/extensions/extensions-hooks.rst @@ -18,7 +18,7 @@ so to clarify what is meant each time, r Hooks ----- -Within |RCM| there are two types of supported hooks. +Within |RCE| there are two types of supported hooks. * **Internal built-in hooks**: The internal |hg|, |git| or |svn| hooks are triggered by different VCS operations, like push, pull, diff --git a/docs/images/ldap-example.png b/docs/images/ldap-example.png deleted file mode 100644 index 418d6d27feb2f71a13afaec03cc20c306dfed58f..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@a>wm%x%wv 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

5N=XHBXBtze_Mv1umwB)g7!Zk;Gk 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�ZelP)ZAiS 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

#w)4kiv*faY*47L+vZoQ=BSh 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 diff --git a/docs/images/saml-onelogin-attributes-example.png b/docs/images/saml-onelogin-attributes-example.png new file mode 100644 index 0000000000000000000000000000000000000000..1ca5a1540bd9a418e63affddf5710f275bc7b10b GIT binary patch literal 35514 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 diff --git a/docs/images/saml-onelogin-config-example.png b/docs/images/saml-onelogin-config-example.png new file mode 100644 index 0000000000000000000000000000000000000000..92f32967e1ba5712fd29c3b423d8ff3c91c8383e GIT binary patch literal 65146 zc%1CKWl&|!wk-%n0foD}L*WjEySux)ySuwn6fT9kyE}!uySuwI^__F?f!DX=MRfn` zcsmvmd#{x%b4q5;kt1gkA}uKd1&$650000bBFrxf0Pv;n{ZHW=$orj@WO`))0PqGA zK0av?K0aJ&TPs5oa{~YX;gE!%pz?AH$bn<62NB4@_PkcUh&}+ayfCfI7RG_HR2Iwfy&OIPc4Op*%^k+abJZJZUvrvqi22a!(D*Sg~?tv7Isw-b|I$1sPV@x2u%9u$Be>n7&&t{Nu*cNi1ErCQrV zxFCS^Ip}&9Yb1X>{Slwkn>GO400FZGlrh0s99RVaQwKH+kkdNUGtW0!D@kieZQ4J;2d!^g#!p9L|_Sb)+5 z3dNf<_dq>`aylY1HXrjn@K96=b$SU`_81*ao=flY0)~ zj0r4FhzOM%R>e&#on8Pd&;A{Zh__S#DQ8H!wm?uGqX@wPf=Hm1--BOoLY@K3!pBkY zDJy3j(;oS~ThTXt@%Xk9-sayHfXyG3)vim5DiO}w2iD!yqZ><0P9u;+Cyup?xJ$PS zZHP+m)2nNutXBdw3xg3P(OcGK*>~E@q_b4nI*)hE0v#mR{cB6z8jppE3PZ_%$&byK zE$XNnYKwDS#2KpvY84bG3S}MQ+~B_Z-tM0Bnf{rO$46V(8s8tb!C$>gu?vtFV}n>H ziAF@85FJ4yIHpH_(^Z$XGRFDWee``IKmI`sricz9Cs~HPbbnRFQO<3fd!;RoHKDlS(cgDnVNafMf}Aj z@`duO#rU~d#)1sUlxcMdj;W46Z%xu?*>~AjTO_W~_ijeq%8RlN%ZB7A6)j|2a~?$^ z%63Z=^XfE81x{jqsIG`}hb|HcZ|~E@rqhb2N!G zGckKG1*^R^)|hrJl$dgylbl&C^pJFu4=UO!>YiIDwA0*U)d(2*G;OD+xQ*x7M z6Z{JLisgz3hh8gb>z7vIR-s3Jk3i4ZSIHOX7g``JKU}{V5Gas4D6l}tZp7}Dz_h@# zi0}yAa3=Ix)CkmK^y=`daEx%AWNGnb@hWjs3Ck2+3A8+uJU0oqVQqT0YQ$>M>XhAz z##Yuf_i~pr_hI+`2j7R0ksCJ4R5R&ssYO!ji412 zH&C}2kDQkRKOa6{zJ&L2^$Tujez@T<*yh?=eb2e?A}6j@u5QXEpDT?rbt)BxEsBCp zvq#Nddm=h49bzfBPvB70Ny7SNLg6s)sL!$3!ELkbY}BFL&a02XAdfDJM$1LnC74~( zrRC24QhjUTG~n{`=KJm8t>R_mW!bssan4@c@zgr~uGwDmQ2h$b7Rq}4N_j?Z?`FlJ ziry>qtJyO(a5ND9m+xQFza|5Rep&Ke$9>Dh;(_sl@&oRq#KZMRsv_8yDX=kC+^5!= zuNbcws>t7%-6-y6BPf9TLjWx#&hN@^8Q~6V08bQh7@QcT39o_k!r4cj#~_H_jO!?I z9KmwK;;C-NPR047jmp!#@@`WjEa>MvC7G(NQ{lyAf>=4FoK|An@v-g0&hZWjJPjBv zSP%kdHQ#*g@ddx4uEHj2e_VCSmG9B$${^NGvZx!j9vvD{jIz;7(^6wxRVtDz1fLj6=^Qn1OdKj~`el?-peM*;+RO%mm1%Svcv zybB~DLw<&;RVT#s<%{X!KSD4Mah}`7kJa9oz-_J74q0c0q^q-C` zyj^o&`;2Cwa?Q*Y&LvEHu#&Qnav*x{%eL})yy%Q{t6{8zh;;AA5DB?Px?)*a)EJkT zWHTmU0`(L2r@y71U#)Bb_OnUSNHU}|u&X$Px)wE{nT}dun~9jj)b-Wgrr&a{OzbW@ z9-{owf=-{{kaJPoIi9uau?yJC8{JF8u|lb9Z;5u-DbRE2_Y12;vC;x>ewSkXa)z8! ziOP|VZA{Cd(RDX@s&;A)ZBET{e$Du_>gdN!@k)7q(@ArR)sJQK6Psg`BaLadva!;$ zF3NH>CCh5dwFU(D{5#9(6`xn4mr20Wj)B%|xAL>S3d-rFzv8yN$x7w zL7O-V<>H!Y&r>gBWtH=l8*|xna#l?33T|%aD_u@lTUlFCBy_~YYbajQPuBYcTY7u> zNm66PO749gRByVExhG~Fb3t-wa&o+N!5mZInXrCwu>j6?umGZ%0Mpn2!C^*EJ7_%B z9Iuc*Z#~rgb;GdqnD(svCtVh0SD{ z7;lgd5Nz@Q+z&F6lg)o6KgtFny5V@=hAeP;s4fmL^NrGYm)cvx#O|+aUBhJ2fU(|I z_%;Io0H&G9E88nei2u;DvY^t@x6(DBa<;Gr`1@gZ{_+0R!oXe!*V)3{((Z>d2mU`L ze!PGGdz%^`_n#v6W*qp+64JPQR<;JXj8qI%H29q0xVX6Nw)%!YWcdaE{rdYa4t!&K zd+Q(6)J{%LR8I6%R<=gew5+VG)HHO|baa&OB`ED&EbVoiDJ|^?{#D6;)x&RKr)O(o zZEs>_iTn4rj;@u1JqJGizsvmV^RI>soK61IlBM0h)p}Qu`tK8JS}Gdq|E~LQ^?&dF zkT!8PFjwX`u`sZ-dvAl2nU;o~`hR@#KfbB*pXZFM%pc!W`MXE) z7;P~CAn>?=|G64Q!Ni+K`^Pro0KlUE`|=j_?n;92`6}%FpUWR0fc7Wghe7~g-vGh& zsDM7z2m1xFPaTAi`^$eW+^?kJjgTL!66XFUjcSJ${i#XVOqyM~k5%CU;&JN?8+@#a z`wQ>8V1K;-k6`}@mZaM?57a-1V0>ny(KvYHLUSDeioKRl9LpHL2c zoHMs|mTgkt($kraUo@DJIV-;0QPF_BujkG)PdYU1b?~s=?lr@6`H!2Dct7Z1oeM_z zyO(8<9TZ}3tpEAJj-UI{I`7D%rE;%lUoRu(H<+Z}tzm5hX$qF6L!rGrccRP4? zKj`PvoGDkXw*B4%`uvHf-u)Hu?fn;+!L~?utAQl9gqfF@ z(z~Nx6*$9b-ssXz%H`+>b+gH=g-8#pvXcY9Y-@)PIOx9JhVbF^md-y=Q?tGWraS^ZFWcvu_C*U#}DMUVioN70klx%jt9ay_&lHhM$+k9g1~ksm3yK ztZcN|;mWrY2st^Jum?g}6x9x^n5(>+XZVM+X~xdw1~tb){>InHapvVK?_0 z8mXG;Wqq|kJoHA#+i5~8V*Kk$+(7hdGxBl3jaHNOp*}V88U|o*QiM@PO4O8@;0aj!gQ!29B*>YTdW=qJP@+HHCnWr-ma`S307+lYZ7faEL zjvzSrTYua@N`}uQXw`+N9vkw@h#cPR3Cjsm3HNQ`+_)zmh7`wD#qMR6dy!Na5nHcp zCyA;37e2g7K1g=9DPLT!z@?O4&?&bAO^cX1X_iQmo9aU2M~ z&F-H7uihh*;whMPHWYjtV^0DcQ=&_z2DwO}1@;n|u;obKWKPwx_RMKL#|xSMBM^auZ$PaUE}$@L9CBPsa)WoT zJp{+5DxQ^{BD6MDB+Pm(TOV^DeUjl2OYHPo$r>Fh;*4)0Q)*!s*TWN>s=;+bdh;Ps z|7$?$vEz#;%&lYYdgLakpoNs*iv!B-@d7#TD>TKQRxGkjknHEs6eB=1W~fcSLtx-&>h{OD+J^)%Ze`_5C#8KE`{l^c z?j(%RyL-(}kzTHE1V2q+H^m5S%0HnOKqGaP)w%<4JG4X=_&mFQz~ffla@N4T3Ypav z4oM+$X9Rn6T~xb?z)ph3X>+u1ilU`|!6->*Bt<<&h3Q#o@=X|+XS5_wJ{%~V_ zLbTQPbW`Wr+&u0g$ete&cD`N2`+N92^JvQuQ#RW<8O`O5gRP5<{|JxkWBe$?_j!-~ z)<{R~t_pv=eqX7>wyzuv(nRXs-8%O$v(japG2FI2WzFY-3S2|$jII=)>+^K9v(zkc zI_tF2eyAM7CgWGPW?L=hK-rM8x#xN!VnAmr71 z1+<9na#4uw>t`kACO0WFD#Sk4Fd1|O#Ny>kDZW0^U?2w}GwNvMqb1|`i*O5;$|7y{ z4^w=H0c=tW^41?zhOpR=p^ux;$N!o(@|f^WZxu@<-F$>d;^6OLJ5D!wUgVPn zBycxgJB6gAU^w5N?bI7GUzad}KFU%{OylurzwCNcs$8MnS$jI9V8NC3+8G4jD+-%3 zVn|3tlxo;sgwQ$k9|3S>!0z2S8`P;teb^IvZFmc%49FyRk-_%>2W$0BMot)%QrKYFJ%_`x0LHq!jm#zJ7nPn`hu|5hM^R?|^7o&g+DX*#6 zj8`Qb$=ZM{k5rKm^@C9t5u}^M1CBQ4p8c^%8pp~qzWhfqr)?F<2@C_25 zAQ6Iqcb9Xv@aGo&iMe%B$EUhSKSC67#CH>i;Eyz!$Xnmg_>f-XD#22oHe*@wCQ-42#Ot(29^ z4BKh-QKUiczvZ7^Pby}y{mDTL%Bz8A>s7lb8qZmz>vN!?rNViCVxIJr-?2nr%%lH) z_`}q`^6mG}Y(iL@L5C`UPyWTFF$t)Ate>Ds(he0E+3`Lh5OOZsz+9B9-B5QvZn%4l zobL9ai05FDD(5Tdg<9brL=f02iv#}D;h=o{pMH+(_2YJ-`WY{4`0eUpB@5S4Xa!vw zs8~BOWmeTm6c^d%KqV=7mvW7E-9{*m__iG7LU|iu4`~(hPrA)GS92j4`~oUingXeh z7agJKdfXGGm0DqP{vq9JLo!KTm(4&g9|&5~t3XiF+iT|&xmMsxsa)R^kv2=LpQxl4 z(Hjn^Mo`k6vW^+s1XVcTbsVJZ;gvW$2Z!Uwq`X!T)Vh>=!^T(*AjPE?2$^&E6KjTg zhT!T#w*i_lbB2uL=VNHPYSg4U(e#S(vSL8`St9PyvJ3;JoFxhIHhT^FPLyiqe#Ez; zR;`)Nyu(;b6)~}k>n&g!a_HE)Bc&T-yYCpSfM|D!TL<}}G2h)ZH(C!UNiI*5LuR`} zvjAED%w;mP!Yq|Wpu&Qh%c*jvzg?LAE^MR!2oB+sYLNAaM%Pcct0}<@%#;YU)KScz zOvm@HyRJ*!5qFY-ozTG(6EclWkRbyb2dIZ*Sm55VQ zr5y7{V4$>-0@p?!guz%Ks&Wj=TqM3O<5}I-RS@|w(%aEd9>pRsOE(UtLC?1m_7W;; zp$ja{nl^$i&izcxNpEx46%T9IDd}ft)*&2nfFEkoFAz2}(w#>EX2t*#<#U~!z6+>~jQ6I|O31WHh==p0G$Tp;Xp z;UQr4A68yD;TH(_7uPWDiB|@SE%N=ccApNG;F>^l35Yr>8SdSXL}af-2et*$;UYbm zX2P$nkk>nOtjl(kig)CD>ah4QF9v=kuIyEA1;vL*D3{0O14J|{IT5Pe$AFbdVuB&M z+siy?Kcg=-x%#k89 zo>y5E$m-#WcD3T%1hHfym7Thd%TkR3_g1usTiy5qQZS;F;((v#%W+?xqDa5BW9R%$IgDB85&5l#lrYEi556UQ zC${Mb23>WRf0j$4?HKIQnbFH+y3$ec;jYVr-E$ep%#%GcT-^3VzrNtxcm++}at6xe zE<56L?J_QA!d!2ma$&cAqZ&%@tGWx!ZqBJZImqwG8eAYRLq6h3K6R{k(pg`r@3A$* z(a(lroWrp7U^6OE6<}9}PZ+=I0Di&m4N2Sy2_wr{Bm3Gcf6$X3pC%;0hEULt>N{VK zK$t*+o=!{^{Wf>LP)W0=%)Z4NKSE+*vN2Stv_;g6YRT6e6aFX7%Gd9mnAnh{o}T2P zd<4`#Dg^j#7B5B3UwsS_g67&Fj?98q;HGJQSfp_qMz;S(sq;b*VP{i7S&QvlS-^W_ zA(@8>l1Ru1OW|F~B@%2lI!{L^x7Z26#J;ZKxgKo(MgL`#xi#&T+1I}#9*>|ynhDCM zWn;!B>O)Y8?TC-168#D6w|KEQu7AWWlnJXI#2!pb`ib>b?__H7&GKDlYFZW@H#p2i*hA3mFxt*gheao7 zCo)^gm(5VlYSyzmJJ(_CO^@#)>zD;#sG05X^iW!;YRb8Crnd>D2F6X*)R-pJNl4KT zk9G&5*b6>V`nAicip(r+G>+#G_MxWtXC!F-2Ny)cezm+8ar?Qw1>f3lqU#KeYCDM^t}#I*u;6Iw?b z6sEc2z_Z%Rj_d?fF616XAduT12<2rZAld6!0=H*cubcoyY_sz16=Yp-r4On?<-0Ti zOp6QAO7ZFwrVTw@=~xj5kp+~@WuI7>BFff(cg;Kyz>Kk=I1(v*W@aZ-NYWuvVo4uC z6L#9{cp|xaOZ4yy{~VN;Qx{SyYB0|8Ig;|gKWbFv8qJ6@B_JG>P5a@!dj6!hM$_BP zE#D=MJMedVlQH40?TKs!c12z78*yZWcOnos@KiQ)1N66uKY*_vU*=kSohL_7*~I_T zcjml1ZnSBkU(Y?E?yEk6TtIkX)OYL)Mvrv-?GyAN(H@OlXXHob zN?q*z{kd4qCF1|Ll}1ZT;goB2xofr{iH$Yjr{l)erC7)fROve z)-U*BJOz4JA(G3qXm|RGOzmCRGi>-@ID>T3eIoCy8^6L2C2ZP)c3*zFPow#+;1*H8 z?Z>-vp#T9F@yU|Vfge};E$83z5kf(=Z`F+Q%788a3)C=ATbsn(VzH zIny7ks2`1X0iQ{OP0gA}@WJLuvhNM$Puttle3WU6=zYDUX-n}5cIWqp_q{;niMB}hQ|{{L>!^(MH!R;+yM&8ew#FtV`>$?gdcbiS?JzRy}la(97ml>sT5 zFV#WK=9Q5HcznabX7S&>d-bNyt&{S4qC&rI3A?2(#_VUS9Ds!XbiRC1g0gMddvf}uSs>l7{kMv!T^Ihv5 zt+OV49$F>on@)*xY}U>e=tuTo6IPg8DU`?k-liN@H5lhrgDf8N^yS_orm0(J-~@qd zLvw_ZxA&2KlhXZS4E0fUse8h!>M+|m5Dfxln8)gg?7E1-fGTnQ$rhmP>gDyBd+9VS znUrZ%#s3(X>>ScDYa^odmC55?b_m+BzWc&~PSz5R=;a1?)yjO531Zh6W4XML?mql0o8i#2BLJFTCTI zeyn|XF~=x6*!mWjo7(+O+mZpsKZ!8VE(2B@ClC{DPUW{1Yn%P)JTgtwE%tbh&zG>6 z8L8gXN;AD5=f7yuukhk2F6SGSR1qV;U{d=5H?IuWT>Sfc+$g2W~ZW+hzPP%{Kpqr>6)UJD<jQl@6ve%iIve22fn5-hhe^oz%-x`Xmy0CC1;$N`+K zl!;>WDp=Ebr--)#YwP5HTgX938JDF3$W>90j*dmyHEz=ft1u8R4-B!T~!!ET( zfI|HN%xS=E|7ZIO;o4xg#np2?^E^Bzqv(kQlPVgAtB6$yYnJZD1uZUe2qJRE=MS!D zty01D^3j-;Ysgtbd?(afkjov24qA6Fn_?l#M!lks&Q=MFvv1%2&8?ZR7MyLQh|ks= zOs{b{zT6&6`pUbEcRJLKnLxp_lg_z`yeYU$>Of3M+C%T<7Bs!teIT{SAd{*;+ToEH zK$C}}qrk%y+k?}1GJq$6SX|R`24Xr}wD)Ox_yXIofdK|K9%WPg8jY_hy5vxW_Ce-X|FlGHE2&2X*E?h6m!v_u$r3Y51uEFmdlL z79WxzjZcAyF55w+yt%$!zZ&D}4}dsqAE5mN?##YS?Qnsu{Vx>fRoZ`1oDmmmELsXbN>V=v z0l=97L-i9v6j#c?CRG|)?;nOUXu|j(f^*$Q_&=hBaIbInh9yr_H8-bpIcF2|tAr(x zGND2?Yy@J|w1=3coP2^Egy$p7Yuqn|G&33+2t&Fh-sEDm0+slkA1Bmw?@5F2>M-rQ zB&qFR1TJd10OmjJ(0(JbZr!i;N9fCn`^c00wK3^m8w(_VYLSrP-Nydu*p8ntQTAUO zBbwBGgfG$Ke{JmQI{yjjguG)S_xDBMz8!Q2hHO-EJ4{&^9^5i!LWjKmF2_<(TXhz> zo;;)yQMbH428?{Rb<~_t-oKnoNbDm*9^UGQRk4sZmuk8egD!7TnawpG_ew0^rXG;E z%Kf!nh{zi0m&+5I_L*bEVn6{JJJt0~(AqWs2nyq5NHR)s#f!4F0sn}@L5!yTyt#Ec zmfm3TfMWxw<-El$xf8wfL>Qoh-2s}V&as+OnA^M?tIoLjJ z%D)x)%Y=HoIVzjM;3sZh{7}>OC?*yW>+7TM8>X19uInZ!vaZdrYHNe^qKvbddY zS#J*K*JNY6=_{v#_dhVlbDl*COx@GNU)W#F(dfdfwkmTTb6xd0H6iD+Kx%&E z?#XIR$Q{Wf7jGwFL@X7JU#-CFr?G_w>J6Rr_@@Rb<~+-U5tk9-vlrZYDuAXGiP`E? zQua&k62rrgt&E$*}$|wKtxMQi3lnB)5(Ui%{i{~gM0*M8egT#D1(H3*@OAHEH&3Z{{Ssr+lWY93$5vj zV23l@zaB$uKk)o(Qr|O!Tu9O18hV+=LXlZKXJn(#Rhh|3Aja$2bjAGb&R*V%N^mMs z)VSDLiD>o@3-~npb7VI4%p!*Lm7>_PCx2Bf4!k4i(z@)vp+Tne%oORU4E?@VdHz&1 z*HTEHo-H34GosIy$RFHl^dY8W)Cqcjk3KUo14yx-wK8>x#_WcZDtZ7IfSv zN)sV4w3n3p1oKhytafsQtC@1Mv{k@D6AeRVtf?*`?DT#4YgV-UH+O6=;=a4@X;`P3 z`Z`FFT;ga&RP7)`+B5x;+J{;Ukh2Yrw(c%1L&L+fhKn?yoISGOt_{{bJI2({O<9NAMV5cdmN^TUBj1#!lKw9) zKlEGb4)4{`C4znas>+=T$}#BCxHfZ+JSx&&vNo5wz)bUPbj&?-koo*|EK=PoA|e-_ zuAK|P7}s&`#OwAv2_~ox1-f z^QFIr&^WNE$uyH>uZYzdyH0E!^DS!hlK#9v^q31vtgp|UI5X9DGebBOK%`by^}Qq! z)xPKBK#y$36Xc*&H|1)jQo&e^@J75#M_Hw+IW^;*jHN5_B!sbEl&;PhgrPY{YS2+F zT%qYrrtW(Q7D< z9U|4KsY?y&X%%?%Jwwh?kM#3Zahz_Q`ff&Q9i1jLwtzhVKj*93k0q3%Etcp&b=B0d z#lKa&wDHZYgE6)FCp9I0+CmVYDZ-)MKkje7N`VoMefh#~TA z%&foN87hr-)NInH!A$&ciSWh&l5#w91-?Pv_z6C%}80+I=0B! zjhUCjUD8KZ1YSDqZtm890KU(lur|C}_>2PmzqVOoQ2xEm5*=o?$R59(;g`3iiO8jl z*z$F|oRq(N!*coMMOXQ`{;S_&?n?L!SFxaHXb~w6>#nM~Yd#v~s4Qasj`q=L+IKv{ zuT)(VrM-A)F;~t=-%m40WGtu8i~$W%f*C8s^`pLq0R!WM3|HH0P1cQh6Xw9|anN7v zSDQu0C{iR^GCy)2#Z3Z}YMlDtXfr?lE|?S!^=5TqESlRbUX0F20XuSaIk(b+7}6vi z7Z9_>u6uQlAg;juLhjJ-I*nx!XlEcfK3O`ncsCQ4Mumh_ZjMZz7FvnxRVYSbXbAVC zR#mm}_kkravT|GPyW-3_6Pn@h2^zeEyQUjN7f?xh|+P`E~c4fob!+lnT0fe1$}7*7L)t|GOLum1JwMiYY^h9$(PE= zR4|htX=dQ@Gm;{vF{KfB1~dq3h%QlK9k_Au^i4c6$BM&u)2lw`fJ>h)4+(FPRZh6q zpa3cf=O0E(1Wepg$6Ofgb|W>BSdr%CvTV2OgqgYUNc|ihgMmln_nU7~0_;Uneu%Du ztU7Wca@TVt7Dq0I3>1XkpBI-Rbg!f!-}Xcd|C}ae zIo1AuZGK_8Y=TQW6(Ir13aLi>h{g{GivKptRvs(iuS9ltRW%g$tR~Xx$!w4)7&Y~l zNMcqSi!_0VCbKD9w)gT!=e0(@ZYUV`UC6N-@Z)C0D4xjbOEC9!8Yp#zij!k*EIvt1 z9p0##s*<)30&JG&rBT=A^4nqH6&AtJBt59#1L%M5p1X`_(XkD7U|7Wrls{X8+}WP# zBO&H_FA-$QV)FzysJJVfFgT2v4x_R&&@N_puH_K;_}tH#E?*O;A;V+w58DwuIN$LzSFLMmUSJ?7hboD6IIQwYVzNa2S;Q zY=JlKKdW{bCg-axHJlOpck(XD!o=fNwQdUU6nBY6(di3*NhvbZbjDkBd};%^@t+*RxS$R~h0OUyCN!V3-eQt;UIBy*8hk-SqJtG-289`tj=^ z8{rZ(dASHgK>|XMacCO}ER*!1kG4j#+Q&@OzWUB#1w-}>IMw8X0{IFUgzXDa)cI`& zei8GA8h^p5QVf5zL6}VTqThCor6SXaza0lL*Q^BC)X8=bap=@fY!f=LJ+6bar6;`! zNs&DAQy5Uvy)Kb(YlevJ)n`kDrCo(vR=^@deqb~qY4L+*I4c+0N>5XEmtu77;XRcg z@07gQnDzZ<#|r7Fpp9))4nvJ4hqOCES0+*?L}9rm6Fg#LUb+m0SCyiL8dt-jxKSyy zHbu|N10IQ=W(a*MaWEReR(`*|Wbm$|P%1Egc;R=*<|c|`HlYSQ(frz?_u|%u9=4^l z|8*|ibnyf7)1&o;oY=<<|w@-Xs(c?;FFke56>Uz7?G7}uSW3DMROqYQathLS|^feR)oS#m=iepdAE2Q z2H-5VOgb8R>q4j{Hf8HM8$N^;q3XtDQZDEz%l!Xt12#}V=PCaWG)TrUJx4$UkT*($(dD99cMOGJ+iA9c4{=gw-|) zrSMIOpP)q5t^BZiicwYWB~ew`t;s!ntHDXUnOwm;(uU-S>{Fx&B+_Md!Ym2AiPp`T z4J|RrFG&yz(^KWX;S^5stQ+mN%0Af6G-j;7ziJO1qP(#dNaqo>5G!dMOAKpO#dh>? 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 Settings --> System Info`. For example, Storage location: /home/{username}/repos. -2. Copy the |repos| that you want |RCM| to manage to this location. +2. Copy the |repos| that you want |RCE| to manage to this location. 3. Remap and rescan the |repos|, see :ref:`remap-rescan` .. important:: - Directories create |repo| groups inside |RCM|. + Directories create |repo| groups inside |RCE|. - Importing adds |RCM| git hooks to your |repos|. + Importing adds |RCE| git hooks to your |repos|. You should verify if custom ``.hg`` or ``.hgrc`` files inside - repositories should be adjusted since |RCM| reads the content of them. + repositories should be adjusted since |RCE| reads the content of them. diff --git a/docs/install/quick-start.rst b/docs/install/quick-start.rst --- a/docs/install/quick-start.rst +++ b/docs/install/quick-start.rst @@ -27,9 +27,12 @@ 2. Run the |RCC| installer and accept th .. code-block:: bash - $ chmod 755 RhodeCode-installer-linux-* + $ chmod +x RhodeCode-installer-linux-* $ ./RhodeCode-installer-linux-* + Do you accept the RhodeCode Control license? + Press [Y] to accept license and [V] to view license text: y + 3. Install a VCS Server, and configure it to start at boot. .. code-block:: bash diff --git a/docs/install/setup-email.rst b/docs/install/setup-email.rst --- a/docs/install/setup-email.rst +++ b/docs/install/setup-email.rst @@ -3,12 +3,12 @@ Set up Email ------------ -To setup email with your |RCM| instance, open the default +To setup email with your |RCE| instance, open the default :file:`/home/{user}/.rccontrol/{instance-id}/rhodecode.ini` file and uncomment and configure the email section. If it is not there, use the below example to insert it. -Once configured you can check the settings for your |RCM| instance on the +Once configured you can check the settings for your |RCE| instance on the :menuselection:`Admin --> Settings --> Email` page. .. code-block:: ini diff --git a/docs/install/using-mysql.rst b/docs/install/using-mysql.rst --- a/docs/install/using-mysql.rst +++ b/docs/install/using-mysql.rst @@ -4,15 +4,15 @@ MySQL or MariaDB ---------------- To use a MySQL or MariaDB database you should install and configure the -database before installing |RCM|. This is because during |RCM| installation +database before installing |RCE|. This is because during |RCE| installation you will setup a connection to your MySQL or MariaDB database. To work with either, use the following steps: 1. Depending on your |os|, install a MySQL or MariaDB database following the appropriate instructions from the `MySQL website`_ or `MariaDB website`_. 2. Configure the database with a username and password which you will use - with |RCM|. -3. Install |RCM|, and during installation select MySQL as your database. + with |RCE|. +3. Install |RCE|, and during installation select MySQL as your database. 4. Enter the following information during the database setup: * Your network IP Address diff --git a/docs/install/using-postgresql.rst b/docs/install/using-postgresql.rst --- a/docs/install/using-postgresql.rst +++ b/docs/install/using-postgresql.rst @@ -4,15 +4,15 @@ PostgreSQL ---------- To use a PostgreSQL database, you should install and configure the database -before installing |RCV|. This is because during |RCV| installation you will +before installing |RCE|. This is because during |RCE| installation you will setup the connection to your PostgreSQL database. To work with PostgreSQL, use the following steps: 1. Depending on your |os|, install a PostgreSQL database following the appropriate instructions from the `PostgreSQL website`_. 2. Configure the database with a username and password, which you will use - with |RCV|. -3. Install |RCV|, and during installation select PostgreSQL as your database. + with |RCE|. +3. Install |RCE|, and during installation select PostgreSQL as your database. 4. Enter the following information during the database setup: * Your network IP Address diff --git a/docs/install/using-sqllite.rst b/docs/install/using-sqllite.rst --- a/docs/install/using-sqllite.rst +++ b/docs/install/using-sqllite.rst @@ -9,15 +9,15 @@ SQLite as it has an internal locking mechanism which can become a performance bottleneck when there are more than 5 concurrent users. -|RCM| installs SQLite as the default database if you do not specify another +|RCE| installs SQLite as the default database if you do not specify another during installation. SQLite is suitable for small teams, projects with a low load, and evaluation purposes since it is built into -|RCM| and does not require any additional database server. +|RCE| and does not require any additional database server. Using MySQL or PostgreSQL in an large setup gives you much greater performance, and while migration tools exist to move from one database type to another, it is better to get it right first time and to immediately use -MySQL or PostgreSQL when you deploy |RCM| in a production environment. +MySQL or PostgreSQL when you deploy |RCE| in a production environment. Migrating From SQLite to PostgreSQL ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/integrations/integrations.rst b/docs/integrations/integrations.rst --- a/docs/integrations/integrations.rst +++ b/docs/integrations/integrations.rst @@ -11,20 +11,20 @@ different Slack channels, for example. Supported integrations ^^^^^^^^^^^^^^^^^^^^^^ -================================ ============ ======================================== -Type/Name |RC| Edition Description -================================ ============ ======================================== -:ref:`integrations-webhook` |RCCEshort| Trigger events as `json` to a custom url -:ref:`integrations-slack` |RCCEshort| Integrate with https://slack.com/ -:ref:`integrations-hipchat` |RCCEshort| Integrate with https://www.hipchat.com/ -:ref:`integrations-email` |RCCEshort| Send repo push commits by email -:ref:`integrations-ci` |RCCEshort| Trigger Builds for Common CI Systems -:ref:`integrations-rcextensions` |RCCEshort| Advanced low-level integration framework +================================ ================== ======================================== +Type/Name RhodeCode Edition Description +================================ ================== ======================================== +:ref:`integrations-webhook` |RCCEshort| Trigger events as `json` to a custom url +:ref:`integrations-slack` |RCCEshort| Integrate with https://slack.com/ +:ref:`integrations-hipchat` |RCCEshort| Integrate with https://www.hipchat.com/ +:ref:`integrations-email` |RCCEshort| Send repo push commits by email +:ref:`integrations-ci` |RCCEshort| Trigger Builds for Common CI Systems +:ref:`integrations-rcextensions` |RCCEshort| Advanced low-level integration framework -:ref:`integrations-jenkins` |RCEEshort| Trigger Builds for Jenkins CI System -:ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference Redmine issues -:ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues -================================ ============ ======================================== +:ref:`integrations-jenkins` |RCEEshort| Trigger Builds for Jenkins CI System +:ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference Redmine issues +:ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues +================================ ================== ======================================== .. _creating-integrations: diff --git a/docs/issue-trackers/issue-trackers.rst b/docs/issue-trackers/issue-trackers.rst --- a/docs/issue-trackers/issue-trackers.rst +++ b/docs/issue-trackers/issue-trackers.rst @@ -9,7 +9,7 @@ You can set an issue tracker connection * At the |repo| level, you can configure an integration with a different issue tracker. -To integrate |RCM| with an issue tracker, you need to define a regular +To integrate |RCE| with an issue tracker, you need to define a regular expression that will fetch the issue ID stored in commit messages, and replace it with a URL. This enables |RCE| to generate a link matching each issue to the target |repo|. diff --git a/docs/known-issues/error-msg-guide.rst b/docs/known-issues/error-msg-guide.rst --- a/docs/known-issues/error-msg-guide.rst +++ b/docs/known-issues/error-msg-guide.rst @@ -7,7 +7,7 @@ Error Message Error creating repository repo-name Cause -As of |RCM| 3.0, a VCS Server is required to run backend operations. +As of |RCE| 3.0, a VCS Server is required to run backend operations. Solution Install a VCS Server. See the `Install a VCS Server`_ section of |RCC| diff --git a/docs/nix/nix.rst b/docs/nix/nix.rst --- a/docs/nix/nix.rst +++ b/docs/nix/nix.rst @@ -3,7 +3,7 @@ Nix Packaging ============= -|RCM| is installed using |Nix Package Manager|. The Nix environment provides +|RCE| is installed using |Nix Package Manager|. The Nix environment provides the following features for maintenance and deployment: * Atomic upgrades and rollbacks diff --git a/docs/python-packages-generated.nix b/docs/python-packages-generated.nix --- a/docs/python-packages-generated.nix +++ b/docs/python-packages-generated.nix @@ -5,11 +5,11 @@ self: super: { "alabaster" = super.buildPythonPackage { - name = "alabaster-0.7.11"; + name = "alabaster-0.7.12"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/3f/46/9346ea429931d80244ab7f11c4fce83671df0b7ae5a60247a2b588592c46/alabaster-0.7.11.tar.gz"; - sha256 = "1mvm69xsn5xf1jc45kdq1mn0yq0pfn54mv2jcww4s1vwqx6iyfxn"; + url = "https://files.pythonhosted.org/packages/cc/b4/ed8dcb0d67d5cfb7f83c4d5463a7614cb1d078ad7ae890c9143edebbf072/alabaster-0.7.12.tar.gz"; + sha256 = "00nwwjj2d2ym4s2kk217x7jkx1hnczc3fvm8yxbqmsp6b0nxfqd6"; }; }; "babel" = super.buildPythonPackage { @@ -24,11 +24,11 @@ self: super: { }; }; "certifi" = super.buildPythonPackage { - name = "certifi-2018.8.24"; + name = "certifi-2018.11.29"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/e1/0f/f8d5e939184547b3bdc6128551b831a62832713aa98c2ccdf8c47ecc7f17/certifi-2018.8.24.tar.gz"; - sha256 = "0f0nhrj9mlrf79iway4578wrsgmjh0fmacl9zv8zjckdy7b90rip"; + url = "https://files.pythonhosted.org/packages/55/54/3ce77783acba5979ce16674fc98b1920d00b01d337cfaaf5db22543505ed/certifi-2018.11.29.tar.gz"; + sha256 = "1dvccavd2fzq4j37w0sznylp92ps14zi6gvlxzm23in0yhzciya7"; }; }; "chardet" = super.buildPythonPackage { @@ -83,31 +83,31 @@ self: super: { }; }; "packaging" = super.buildPythonPackage { - name = "packaging-17.1"; + name = "packaging-18.0"; doCheck = false; propagatedBuildInputs = [ self."pyparsing" self."six" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/77/32/439f47be99809c12ef2da8b60a2c47987786d2c6c9205549dd6ef95df8bd/packaging-17.1.tar.gz"; - sha256 = "0nrpayk8kij1zm9sjnk38ldz3a6705ggvw8ljylqbrb4vmqbf6gh"; + url = "https://files.pythonhosted.org/packages/cf/50/1f10d2626df0aa97ce6b62cf6ebe14f605f4e101234f7748b8da4138a8ed/packaging-18.0.tar.gz"; + sha256 = "01wq9c53ix5rz6qg2c98gy8n4ff768rmanifm8m5jpjiaizj51h8"; }; }; "pygments" = super.buildPythonPackage { - name = "pygments-2.2.0"; + name = "pygments-2.3.0"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz"; - sha256 = "1k78qdvir1yb1c634nkv6rbga8wv4289xarghmsbbvzhvr311bnv"; + url = "https://files.pythonhosted.org/packages/63/a2/91c31c4831853dedca2a08a0f94d788fc26a48f7281c99a303769ad2721b/Pygments-2.3.0.tar.gz"; + sha256 = "1z34ms51dh4jq4h3cizp7vd1dmsxcbvffkjsd2xxfav22nn6lrl2"; }; }; "pyparsing" = super.buildPythonPackage { - name = "pyparsing-2.2.0"; + name = "pyparsing-2.3.0"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/3c/ec/a94f8cf7274ea60b5413df054f82a8980523efd712ec55a59e7c3357cf7c/pyparsing-2.2.0.tar.gz"; - sha256 = "016b9gh606aa44sq92jslm89bg874ia0yyiyb643fa6dgbsbqch8"; + url = "https://files.pythonhosted.org/packages/d0/09/3e6a5eeb6e04467b737d55f8bba15247ac0876f98fae659e58cd744430c6/pyparsing-2.3.0.tar.gz"; + sha256 = "14k5v7n3xqw8kzf42x06bzp184spnlkya2dpjyflax6l3yrallzk"; }; }; "pytz" = super.buildPythonPackage { @@ -119,7 +119,7 @@ self: super: { }; }; "requests" = super.buildPythonPackage { - name = "requests-2.19.1"; + name = "requests-2.20.1"; doCheck = false; propagatedBuildInputs = [ self."chardet" @@ -128,16 +128,16 @@ self: super: { self."certifi" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/54/1f/782a5734931ddf2e1494e4cd615a51ff98e1879cbe9eecbdfeaf09aa75e9/requests-2.19.1.tar.gz"; - sha256 = "0snf8xxdzsgh1x2zv3vilvbrv9jbpmnfagzzb1rjmmvflckdh8pc"; + url = "https://files.pythonhosted.org/packages/40/35/298c36d839547b50822985a2cf0611b3b978a5ab7a5af5562b8ebe3e1369/requests-2.20.1.tar.gz"; + sha256 = "0qzj6cgv3k9wyj7wlxgz7xq0cfg4jbbkfm24pp8dnhczwl31527a"; }; }; "setuptools" = super.buildPythonPackage { - name = "setuptools-40.2.0"; + name = "setuptools-40.6.2"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/ef/1d/201c13e353956a1c840f5d0fbf0461bd45bbd678ea4843ebf25924e8984c/setuptools-40.2.0.zip"; - sha256 = "19ng5m7kigllg3x96c91y3a2k28g6kwnbb1v4warrnp4xma1v227"; + url = "https://files.pythonhosted.org/packages/b0/d1/8acb42f391cba52e35b131e442e80deffbb8d0676b93261d761b1f0ef8fb/setuptools-40.6.2.zip"; + sha256 = "0r2c5hapirlzm34h7pl1lgkm6gk7bcrlrdj28qgsvaqg3f74vfw6"; }; }; "six" = super.buildPythonPackage { @@ -157,7 +157,7 @@ self: super: { }; }; "sphinx" = super.buildPythonPackage { - name = "sphinx-1.7.8"; + name = "sphinx-1.8.2"; doCheck = false; propagatedBuildInputs = [ self."six" @@ -175,8 +175,8 @@ self: super: { self."typing" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/ac/54/4ef326d0c654da1ed91341a7a1f43efc18a8c770ddd2b8e45df97cb79d82/Sphinx-1.7.8.tar.gz"; - sha256 = "1ryz0w4c31930f1br2sjwrxwx9cmsy7cqdb0d81g98n9bj250w50"; + url = "https://files.pythonhosted.org/packages/4c/ea/7388faba7cf02999e1bc42f6a8eb1ea0120aec3dd93474cee21cea2d693f/Sphinx-1.8.2.tar.gz"; + sha256 = "1sia2h5rfzy76rbsd69ghr8bbidhsjzzinf3f523dcmivp5k41qj"; }; }; "sphinx-rtd-theme" = super.buildPythonPackage { @@ -207,11 +207,11 @@ self: super: { }; }; "urllib3" = super.buildPythonPackage { - name = "urllib3-1.23"; + name = "urllib3-1.24.1"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/3c/d2/dc5471622bd200db1cd9319e02e71bc655e9ea27b8e0ce65fc69de0dac15/urllib3-1.23.tar.gz"; - sha256 = "1bvbd35q3zdcd7gsv38fwpizy7p06dr0154g5gfybrvnbvhwb2m6"; + url = "https://files.pythonhosted.org/packages/b1/53/37d82ab391393565f2f831b8eedbffd57db5a718216f82f1a8b4d381a1c1/urllib3-1.24.1.tar.gz"; + sha256 = "08lwd9f3hqznyf32vnzwvp87pchx062nkbgyrf67rwlkgj0jk5fy"; }; }; diff --git a/docs/release-notes/release-notes-3.0.0.rst b/docs/release-notes/release-notes-3.0.0.rst --- a/docs/release-notes/release-notes-3.0.0.rst +++ b/docs/release-notes/release-notes-3.0.0.rst @@ -1,7 +1,7 @@ |RCE| 3.0.0 |RNS| ----------------- -As |RCM| 3.0 is a big release, the release notes have been split into the following sections: +As |RCE| 3.0 is a big release, the release notes have been split into the following sections: * :ref:`general-rn-ref` * :ref:`security-rn-ref` diff --git a/docs/release-notes/release-notes-4.15.0.rst b/docs/release-notes/release-notes-4.15.0.rst new file mode 100644 --- /dev/null +++ b/docs/release-notes/release-notes-4.15.0.rst @@ -0,0 +1,81 @@ +|RCE| 4.15.0 |RNS| +------------------ + +Release Date +^^^^^^^^^^^^ + +- 2018-12-10 + + +New Features +^^^^^^^^^^^^ + +- Authentication: Added SAML 2.0 Authentication, with support of OneLogin and DUO Security. +- Core: add debug mode that switches logging to debug. + It's no longer required to reconfigure all logging. A `debug=true` set in .ini file + does it automatically. + + +General +^^^^^^^ + +- Authentication: rename oauth to external identity as it would now be serving both + oAuth and SAML. +- Authentication: allow setting extern type with registration. + This will allow external identity plugins to define proper externs instead of always + using "rhodecode" one. +- Authentication: show if plugin is activated and enabled in the list. +- Authentication: add better logging for ldap related attributes to help track + LDAP connection problems more easily. +- Visual: add change logo header template +- UI: updated error pages style to be consistent with other pages. +- Utils: updated request generation so ishell can run some automation scripts. +- Docs: updated documentation for SVN 1.10 Wandisco repositories. +- System info: expose base_url set in .ini file. +- Style: update pygments template styling. +- Style: updated li style and markdown style. +- Dependencies: added python-saml library. +- Dependencies: bumped hgsubversion to 1.9.3 release. +- Dependencies: bumped gevent to 1.3.7 release. +- Dependencies: bumped lxml to 4.2.5 release. +- Dependencies: bumped gevent to 1.3.7 release. +- Dependencies: bumped alembic to 1.0.5 release. +- Dependencies: bumped peppercorn to 0.6 release. +- Dependencies: bumped pyotp to 2.2.7 release. +- Dependencies: bumped deform to 2.0.7 release +- Dependencies: bumped py-gfm to 0.1.4 release. +- Dependencies: bumped colander to 1.5.1 release +- Dependencies: bumped appenlight-client to 0.6.26 release. +- Dependencies: bumped bleach to 3.0.2 release. +- Dependencies: bumped pygments to 2.3.0 + + +Security +^^^^^^^^ + +- Mercurial: support evolve sub-commands when checking for permissions. + Those defaulted to write, while only read is required for evolve. +- auth/security: enforce that external users cannot reset their password. + External users don't use RhodeCode passwords, so resetting them shouldn't be allowed. + + +Performance +^^^^^^^^^^^ + +- Markdown: use lazy loaded markdown initialization to speed up app startup. +- Gevent: changed DNS resolver to ares for better stability on long running processes. + + +Fixes +^^^^^ + +- Default Reviewers: use target repo owner as default reviewer in case of CE edition. +- LDAP: ensure the proper cert files and dirs are set. + It's also now possible to specify custom paths for those. +- Markdown: fixed auto checkbox generation from markdown code + + +Upgrade notes +^^^^^^^^^^^^^ + +- LDAP cert dirs \ No newline at end of file diff --git a/docs/release-notes/release-notes.rst b/docs/release-notes/release-notes.rst --- a/docs/release-notes/release-notes.rst +++ b/docs/release-notes/release-notes.rst @@ -9,6 +9,7 @@ Release Notes .. toctree:: :maxdepth: 1 + release-notes-4.15.0.rst release-notes-4.14.1.rst release-notes-4.14.0.rst release-notes-4.13.3.rst diff --git a/docs/requirements_docs.txt b/docs/requirements_docs.txt --- a/docs/requirements_docs.txt +++ b/docs/requirements_docs.txt @@ -1,8 +1,8 @@ -sphinx==1.7.8 +sphinx==1.8.2 six==1.11.0 sphinx_rtd_theme==0.4.1 docutils==0.14.0 -pygments==2.2.0 +pygments==2.3.0 markupsafe==1.0.0 jinja2==2.9.6 pytz==2018.4 diff --git a/docs/tools/install-tools.rst b/docs/tools/install-tools.rst --- a/docs/tools/install-tools.rst +++ b/docs/tools/install-tools.rst @@ -44,10 +44,10 @@ following example: Installing |RCT| ^^^^^^^^^^^^^^^^ -|RCT| enable you to automate many of the most common |RCM| functions through +|RCT| enable you to automate many of the most common |RCE| functions through the API. Installing them on a local machine lets you carry out maintenance on the server remotely. Once installed you can use them to index your |repos| -to setup full-text search, strip commits, or install |RC| Extensions for +to setup full-text search, strip commits, or install RhodeCode Extensions for additional functionality. For more detailed instructions about using |RCT| for indexing and full-text diff --git a/docs/tools/rhodecode-tools.rst b/docs/tools/rhodecode-tools.rst --- a/docs/tools/rhodecode-tools.rst +++ b/docs/tools/rhodecode-tools.rst @@ -3,7 +3,7 @@ |RCT| ===== -|RCT| enable you to automate many of the most common |RCM| functions through +|RCT| enable you to automate many of the most common |RCE| functions through the API. .. toctree:: diff --git a/docs/tools/tools-cli.rst b/docs/tools/tools-cli.rst --- a/docs/tools/tools-cli.rst +++ b/docs/tools/tools-cli.rst @@ -16,7 +16,7 @@ rhodecode-tools --------------- Use |RCT| to setup automation, run the indexer, and install extensions for -your |RCM| instances. Options: +your |RCE| instances. Options: .. rst-class:: dl-horizontal @@ -49,7 +49,7 @@ Example usage: rhodecode-api ------------- -The |RC| API lets you connect to |RCE| and carry out management tasks from a +The RhodeCode API lets you connect to |RCE| and carry out management tasks from a remote machine, for more information about the API, see the :ref:`api`. To pass arguments on the command-line use the ``method:option`` syntax. @@ -117,7 +117,7 @@ Options: rhodecode-cleanup-gists ----------------------- -Use this to delete gists within |RCM|. Options: +Use this to delete gists within |RCE|. Options: .. rst-class:: dl-horizontal @@ -166,7 +166,7 @@ Example usage: rhodecode-cleanup-repos ----------------------- -Use this to manage |repos| and |repo| groups within |RCM|. Options: +Use this to manage |repos| and |repo| groups within |RCE|. Options: .. rst-class:: dl-horizontal @@ -280,7 +280,7 @@ the using :ref:`integrations-rcextension rhodecode-gist -------------- -Use this to create, list, show, or delete gists within |RCM|. Options: +Use this to create, list, show, or delete gists within |RCE|. Options: .. rst-class:: dl-horizontal diff --git a/docs/tools/tools-overview.rst b/docs/tools/tools-overview.rst --- a/docs/tools/tools-overview.rst +++ b/docs/tools/tools-overview.rst @@ -7,7 +7,7 @@ To install |RCT| correctly, see the inst :ref:`install-tools`, and :ref:`config-rhoderc`. Once |RCT| is installed, and the :file:`/home/{user}/.rhoderc` file is -configured you can then use |RCT| on each |RCM| instance to carry out admin +configured you can then use |RCT| on each |RCE| instance to carry out admin tasks. Use the following example to configure that file, and once configured see the :ref:`tools-cli` for more details. diff --git a/docs/tutorials/deploy-from-host.rst b/docs/tutorials/deploy-from-host.rst --- a/docs/tutorials/deploy-from-host.rst +++ b/docs/tutorials/deploy-from-host.rst @@ -95,10 +95,10 @@ but below is the example shortcut. # Check that the script is uploaded to your home directory $ ls -1 - RhodeCode-installer-linux-391_b1a804c4d69b_d6c087d520e3 + RhodeCode-installer-linux-buildYYYYXXXX_ZZZZ # Change the script permissions - $ chmod 755 RhodeCode-installer-linux* + $ chmod +x RhodeCode-installer-linux* # Run the installer and accept the prompts $ ./RhodeCode-installer-linux-* diff --git a/docs/tutorials/git-lfs-ext.rst b/docs/tutorials/git-lfs-ext.rst --- a/docs/tutorials/git-lfs-ext.rst +++ b/docs/tutorials/git-lfs-ext.rst @@ -7,13 +7,13 @@ Git Large File Storage (or LFS) is a new, open-source extension to Git that aims to improve handling of large files. It does this by replacing large files in your repository—such as graphics and videos—with simple text pointers. -|RC| Server includes an embedded LFS object store server, allowing storage of +RhodeCode Server includes an embedded LFS object store server, allowing storage of large files without the need for an external object store. Git LFS is disabled by default, globally, and for each individual repository. .. note:: - |RC| implements V2 API of Git LFS. Please make sure your git client is + RhodeCode implements V2 API of Git LFS. Please make sure your git client is using the latest version (2.0.X recommended) to leverage full feature set of the V2 API. @@ -22,7 +22,7 @@ Git LFS is disabled by default, globally Enabling Git LFS ++++++++++++++++ -Git LFS is disabled by default within |RC| Server. +Git LFS is disabled by default within RhodeCode Server. To enable Git LFS Globally: @@ -87,7 +87,7 @@ size in bytes. For example:: The object itself will be uploaded to a separate location via the Git LFS Batch API. -The transfer is validated and authorized by |RC| server itself. +The transfer is validated and authorized by RhodeCode server itself. If give repository has Git LFS disabled, a proper message will be sent back to the client and upload of LFS objects will be forbidden. diff --git a/docs/tutorials/hg-large-ext.rst b/docs/tutorials/hg-large-ext.rst --- a/docs/tutorials/hg-large-ext.rst +++ b/docs/tutorials/hg-large-ext.rst @@ -13,7 +13,7 @@ of the current revision. This saves both Enabling HG Largefiles ++++++++++++++++++++++ -Mercurial Largefiles extension is disabled by default within |RC| Server. +Mercurial Largefiles extension is disabled by default within RhodeCode Server. To enable Mercurial Largefiles Globally: diff --git a/docs/tutorials/windows-to-linux.rst b/docs/tutorials/windows-to-linux.rst --- a/docs/tutorials/windows-to-linux.rst +++ b/docs/tutorials/windows-to-linux.rst @@ -25,7 +25,7 @@ Pre-requisites * For MySQL, do not use `localhost` in the database connection string of the :file:`rhodecode.ini` file. * InnoDB must be the database tables engine. -* Contact |RC| for a new licence Key/Token pair. If you don't, a trial licence +* Contact RhodeCode for a new licence Key/Token pair. If you don't, a trial licence will be applied so you are not locked out of the upgraded instance. You can find the specific instructions to carry out these pre-requisite steps diff --git a/docs/usage/basic-vcs-commands.rst b/docs/usage/basic-vcs-commands.rst --- a/docs/usage/basic-vcs-commands.rst +++ b/docs/usage/basic-vcs-commands.rst @@ -3,19 +3,19 @@ Getting Started with VCS ------------------------ -When using |RCM|, you will be working with |git|, |svn| or |hg| |repos| from the +When using |RCE|, you will be working with |git|, |svn| or |hg| |repos| from the command line or using a GUI client such as Tortoise, Tower or SourceTree. -|RCM| uses a standard |git|, |svn| and |hg| protocols. So all tools that +|RCE| uses a standard |git|, |svn| and |hg| protocols. So all tools that can interact with there protocols are supported, including Eclipse or PyCharm plugins. If you have never used either before, the following information should help you set up your local machine so that you can sync changes with the -|RCM| server. +|RCE| server. -All of the following instructions assume you have a |RCM| account, +All of the following instructions assume you have a |RCE| account, and you can access your |repos| from the web interface. .. note:: diff --git a/docs/usage/file-editing.rst b/docs/usage/file-editing.rst --- a/docs/usage/file-editing.rst +++ b/docs/usage/file-editing.rst @@ -3,14 +3,14 @@ File Editing To edit files using the online editor, use the following steps. -1. From the |RCM| interface, select :menuselection:`Admin --> Repositories` +1. From the |RCE| interface, select :menuselection:`Admin --> Repositories` 2. Select the |repo| in which you want to edit a file. 3. Select the :guilabel:`file` view of the |repo|, and double-click on the file. 4. To open the editor, select the :guilabel:`edit on branch:default` button. - * If the filename has an extension |RCM| recognises, + * If the filename has an extension |RCE| recognises, the syntax highlighting will appear automatically. - * If the filename does not have an extension |RCM| recognises, + * If the filename does not have an extension |RCE| recognises, you can set the language syntax highlighter by choosing from the file type drop down menu. 5. To save your changes, select :guilabel:`Commit changes` diff --git a/docs/usage/gist-editing.rst b/docs/usage/gist-editing.rst --- a/docs/usage/gist-editing.rst +++ b/docs/usage/gist-editing.rst @@ -4,7 +4,7 @@ Gist Editing ^^^^^^^^^^^^ Gists are standalone files that only the creator can edit. To work with -gists, click on the :guilabel:`Gists` tab on the |RCM| header. The gist +gists, click on the :guilabel:`Gists` tab on the |RCE| header. The gist editor also has syntax highlighting. You can set the following properties for each gist: @@ -13,7 +13,7 @@ You can set the following properties for and will show up in searches. * :guilabel:`Gist Lifetime`: You can set a gist to expire after a set period by using the :guilabel:`Gist Lifetime` dropdown menu. - This means that when the gist expires it will be deleted from the |RCM| + This means that when the gist expires it will be deleted from the |RCE| gist database. * :guilabel:`Private`: This means that the gist will not show up in searches. * :guilabel:`Gist access level`: If you create a private gist you can have diff --git a/docs/usage/online-editing.rst b/docs/usage/online-editing.rst --- a/docs/usage/online-editing.rst +++ b/docs/usage/online-editing.rst @@ -1,7 +1,7 @@ Online Editing -------------- -|RCM| has an integrated online editor, allowing you to edit files in the +|RCE| has an integrated online editor, allowing you to edit files in the browser. The online editor has syntax highlighting and the ability to fork, merge, and commit changes to files. diff --git a/pkgs/node-packages.nix b/pkgs/node-packages.nix --- a/pkgs/node-packages.nix +++ b/pkgs/node-packages.nix @@ -13,13 +13,13 @@ let sha512 = "tx5TauYSmzsIvmSqepUPDYbs4/Ejz2XbZ1IkD7JEGqkdNUJlh+9KU85G56Tfdk/xjEZ8zorFfN09OSwiMrIQWA=="; }; }; - "@polymer/iron-a11y-announcer-3.0.1" = { + "@polymer/iron-a11y-announcer-3.0.2" = { name = "_at_polymer_slash_iron-a11y-announcer"; packageName = "@polymer/iron-a11y-announcer"; - version = "3.0.1"; - src = fetchurl { - url = "https://registry.npmjs.org/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.0.1.tgz"; - sha512 = "Xiqmpz0AEEbMNGYPpbrXBIrcI/xaR4tn77pmSLfxVKGGwjEUR/YrRgyIwXp4EN7lvst1dFC8kyl2hLga0uDIVQ=="; + version = "3.0.2"; + src = fetchurl { + url = "https://registry.npmjs.org/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.0.2.tgz"; + sha512 = "LqnMF39mXyxSSRbTHRzGbcJS8nU0NVTo2raBOgOlpxw5yfGJUVcwaTJ/qy5NtWCZLRfa4suycf0oAkuUjHTXHQ=="; }; }; "@polymer/iron-a11y-keys-3.0.1" = { @@ -229,13 +229,13 @@ let sha1 = "e7365648c1b42136a59c7d5040637b3b5c83b614"; }; }; - "@types/node-6.14.0" = { + "@types/node-6.14.2" = { name = "_at_types_slash_node"; packageName = "@types/node"; - version = "6.14.0"; - src = fetchurl { - url = "https://registry.npmjs.org/@types/node/-/node-6.14.0.tgz"; - sha512 = "6tQyh4Q4B5pECcXBOQDZ5KjyBIxRZGzrweGPM47sAYTdVG4+7R+2EGMTmp0h6ZwgqHrFRCeg2gdhsG9xXEl2Sg=="; + version = "6.14.2"; + src = fetchurl { + url = "https://registry.npmjs.org/@types/node/-/node-6.14.2.tgz"; + sha512 = "JWB3xaVfsfnFY8Ofc9rTB/op0fqqTSqy4vBcVk1LuRJvta7KTX+D//fCkiTMeLGhdr2EbFZzQjC97gvmPilk9Q=="; }; }; "@types/parse5-2.2.34" = { @@ -409,22 +409,22 @@ let sha512 = "mJ3QKWtCchL1vhU/kZlJnLPuQZnlDOdZsyP0bbLWPGdYsQDnSBvyTLhzwBA3QAMlzEL9V4JHygEmK6/OTEyytA=="; }; }; - "@webcomponents/shadycss-1.5.2" = { + "@webcomponents/shadycss-1.6.0" = { name = "_at_webcomponents_slash_shadycss"; packageName = "@webcomponents/shadycss"; - version = "1.5.2"; - src = fetchurl { - url = "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.5.2.tgz"; - sha512 = "0OyrmVc7S+INtzoqP2ofAo+OdVn2Nj0Qvq4wD9FEGN7nMmLRxaD2mzy6hD6EslzxUSuGH302CDU4KXiY66SEqg=="; - }; - }; - "@webcomponents/webcomponentsjs-2.1.3" = { + version = "1.6.0"; + src = fetchurl { + url = "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.6.0.tgz"; + sha512 = "iURGZZU6BaiRJtGgjMn208QxPkY11QwT/VmuHNa4Yb+kJxU/WODe4C8b0LDOtnk4KJzJg50hCfwvPRAjePEzbA=="; + }; + }; + "@webcomponents/webcomponentsjs-2.2.1" = { name = "_at_webcomponents_slash_webcomponentsjs"; packageName = "@webcomponents/webcomponentsjs"; - version = "2.1.3"; - src = fetchurl { - url = "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.1.3.tgz"; - sha512 = "0UHJNY88lR3pnEYtBVT7F8cuuxOiITQGWJa0LxoELqkBSB7IabzJFOj5K99PajD3CGAsWpjB0CAeijfe376Y1w=="; + version = "2.2.1"; + src = fetchurl { + url = "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.2.1.tgz"; + sha512 = "lZZ+Lkke6JhsJcQQqSVk1Pny6/8y4qhJ98LO7a/MwBSRO8WqHqK1X2vscfeL8vOnYGFnmBUyVG95lwYv/AXyLQ=="; }; }; "@xtuc/ieee754-1.2.0" = { @@ -499,13 +499,13 @@ let sha1 = "82ffb02b29e662ae53bdc20af15947706739c536"; }; }; - "ajv-6.5.4" = { + "ajv-6.6.1" = { name = "ajv"; packageName = "ajv"; - version = "6.5.4"; - src = fetchurl { - url = "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz"; - sha512 = "4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg=="; + version = "6.6.1"; + src = fetchurl { + url = "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz"; + sha512 = "ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww=="; }; }; "ajv-keywords-3.2.0" = { @@ -1454,13 +1454,13 @@ let sha512 = "DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg=="; }; }; - "bluebird-3.5.2" = { + "bluebird-3.5.3" = { name = "bluebird"; packageName = "bluebird"; - version = "3.5.2"; - src = fetchurl { - url = "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz"; - sha512 = "dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg=="; + version = "3.5.3"; + src = fetchurl { + url = "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz"; + sha512 = "/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="; }; }; "bn.js-4.11.8" = { @@ -1661,13 +1661,13 @@ let sha1 = "9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"; }; }; - "camelcase-4.1.0" = { + "camelcase-5.0.0" = { name = "camelcase"; packageName = "camelcase"; - version = "4.1.0"; - src = fetchurl { - url = "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz"; - sha1 = "d545635be1e33c542649c69173e5de6acfae34dd"; + version = "5.0.0"; + src = fetchurl { + url = "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz"; + sha512 = "faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA=="; }; }; "caniuse-api-1.6.1" = { @@ -1679,22 +1679,22 @@ let sha1 = "b534e7c734c4f81ec5fbe8aca2ad24354b962c6c"; }; }; - "caniuse-db-1.0.30000900" = { + "caniuse-db-1.0.30000912" = { name = "caniuse-db"; packageName = "caniuse-db"; - version = "1.0.30000900"; - src = fetchurl { - url = "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000900.tgz"; - sha512 = "fvicVRDlhHIQpt/bmbLl3hDHKUZb5ZP8O2OuZLz2fSEPlUBbvwwbhhqhGS617ldN6bDoo9A3+MQKQyFq0p7UXA=="; - }; - }; - "caniuse-lite-1.0.30000900" = { + version = "1.0.30000912"; + src = fetchurl { + url = "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000912.tgz"; + sha512 = "uiepPdHcJ06Na9t15L5l+pp3NWQU4IETbmleghD6tqCqbIYqhHSu7nVfbK2gqPjfy+9jl/wHF1UQlyTszh9tJQ=="; + }; + }; + "caniuse-lite-1.0.30000912" = { name = "caniuse-lite"; packageName = "caniuse-lite"; - version = "1.0.30000900"; - src = fetchurl { - url = "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000900.tgz"; - sha512 = "xDVs8pBFr6bzq9pXUkLKpGQQnzsF/l6/yX38UnCkTcUcwC0rDl1NGZGildcJVTU+uGBxfsyniK/ZWagPNn1Oqw=="; + version = "1.0.30000912"; + src = fetchurl { + url = "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000912.tgz"; + sha512 = "M3zAtV36U+xw5mMROlTXpAHClmPAor6GPKAMD5Yi7glCB5sbMPFtnQ3rGpk4XqPdUrrTIaVYSJZxREZWNy8QJg=="; }; }; "caseless-0.12.0" = { @@ -1814,13 +1814,13 @@ let sha1 = "22817534f24bfa4950c34d532d48ecbc621b8c14"; }; }; - "clipboard-2.0.1" = { + "clipboard-2.0.4" = { name = "clipboard"; packageName = "clipboard"; - version = "2.0.1"; - src = fetchurl { - url = "https://registry.npmjs.org/clipboard/-/clipboard-2.0.1.tgz"; - sha512 = "7yhQBmtN+uYZmfRjjVjKa0dZdWuabzpSKGtyQZN+9C8xlC788SSJjOHWh7tzurfwTqTD5UDYAhIv5fRJg3sHjQ=="; + version = "2.0.4"; + src = fetchurl { + url = "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz"; + sha512 = "Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ=="; }; }; "cliui-2.1.0" = { @@ -2093,13 +2093,13 @@ let sha1 = "676f6eb3c39997c2ee1ac3a924fd6124748f578d"; }; }; - "copy-webpack-plugin-4.5.4" = { + "copy-webpack-plugin-4.6.0" = { name = "copy-webpack-plugin"; packageName = "copy-webpack-plugin"; - version = "4.5.4"; - src = fetchurl { - url = "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.4.tgz"; - sha512 = "0lstlEyj74OAtYMrDxlNZsU7cwFijAI3Ofz2fD6Mpo9r4xCv4yegfa3uHIKvZY1NSuOtE9nvG6TAhJ+uz9gDaQ=="; + version = "4.6.0"; + src = fetchurl { + url = "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz"; + sha512 = "Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA=="; }; }; "core-js-2.5.7" = { @@ -2201,13 +2201,13 @@ let sha1 = "2b3a110539c5355f1cd8d314623e870b121ec858"; }; }; - "css-selector-tokenizer-0.7.0" = { + "css-selector-tokenizer-0.7.1" = { name = "css-selector-tokenizer"; packageName = "css-selector-tokenizer"; - version = "0.7.0"; - src = fetchurl { - url = "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz"; - sha1 = "e6988474ae8c953477bf5e7efecfceccd9cf4c86"; + version = "0.7.1"; + src = fetchurl { + url = "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz"; + sha512 = "xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA=="; }; }; "css-what-2.1.2" = { @@ -2318,15 +2318,6 @@ let sha1 = "f6534d15148269b20352e7bee26f501f9a191290"; }; }; - "decamelize-2.0.0" = { - name = "decamelize"; - packageName = "decamelize"; - version = "2.0.0"; - src = fetchurl { - url = "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz"; - sha512 = "Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg=="; - }; - }; "decode-uri-component-0.2.0" = { name = "decode-uri-component"; packageName = "decode-uri-component"; @@ -2561,13 +2552,13 @@ let sha1 = "3a83a904e54353287874c564b7549386849a98c9"; }; }; - "electron-to-chromium-1.3.82" = { + "electron-to-chromium-1.3.85" = { name = "electron-to-chromium"; packageName = "electron-to-chromium"; - version = "1.3.82"; - src = fetchurl { - url = "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.82.tgz"; - sha512 = "NI4nB2IWGcU4JVT1AE8kBb/dFor4zjLHMLsOROPahppeHrR0FG5uslxMmkp/thO1MvPjM2xhlKoY29/I60s0ew=="; + version = "1.3.85"; + src = fetchurl { + url = "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.85.tgz"; + sha512 = "kWSDVVF9t3mft2OHVZy4K85X2beP6c6mFm3teFS/mLSDJpQwuFIWHrULCX+w6H1E55ZYmFRlT+ATAFRwhrYzsw=="; }; }; "elliptic-6.4.1" = { @@ -2912,13 +2903,13 @@ let sha1 = "d5142c0caee6b1189f87d3a76111064f86c8bbf2"; }; }; - "fastparse-1.1.1" = { + "fastparse-1.1.2" = { name = "fastparse"; packageName = "fastparse"; - version = "1.1.1"; - src = fetchurl { - url = "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz"; - sha1 = "d1e2643b38a94d7583b479060e6c4affc94071f8"; + version = "1.1.2"; + src = fetchurl { + url = "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz"; + sha512 = "483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="; }; }; "favico.js-0.3.10" = { @@ -3308,13 +3299,13 @@ let sha1 = "15a4806a57547cb2d2dbf27f42e89a8c3451b364"; }; }; - "graceful-fs-4.1.11" = { + "graceful-fs-4.1.15" = { name = "graceful-fs"; packageName = "graceful-fs"; - version = "4.1.11"; - src = fetchurl { - url = "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz"; - sha1 = "0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"; + version = "4.1.15"; + src = fetchurl { + url = "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz"; + sha512 = "6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="; }; }; "grunt-0.4.5" = { @@ -3326,13 +3317,13 @@ let sha1 = "56937cd5194324adff6d207631832a9d6ba4e7f0"; }; }; - "grunt-cli-1.3.1" = { + "grunt-cli-1.3.2" = { name = "grunt-cli"; packageName = "grunt-cli"; - version = "1.3.1"; - src = fetchurl { - url = "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.3.1.tgz"; - sha512 = "UwBRu/QpAjDc53DRLEkyilFdL0zenpxu+fddTIlsF/KJqdNcHaQmvyu1W3cDesZ9rqqZdKK5A8+QDIyLUEWoZQ=="; + version = "1.3.2"; + src = fetchurl { + url = "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.3.2.tgz"; + sha512 = "8OHDiZZkcptxVXtMfDxJvmN7MVJNE8L/yIcPb4HB7TlyFD1kDvjHrb62uhySsU14wJx9ORMnTuhRMQ40lH/orQ=="; }; }; "grunt-contrib-concat-0.5.1" = { @@ -4631,13 +4622,13 @@ let sha1 = "6d4524e8b955f95d4f5b58851ce21dd72fb4e952"; }; }; - "lru-cache-4.1.3" = { + "lru-cache-4.1.5" = { name = "lru-cache"; packageName = "lru-cache"; - version = "4.1.3"; - src = fetchurl { - url = "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz"; - sha512 = "fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA=="; + version = "4.1.5"; + src = fetchurl { + url = "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz"; + sha512 = "sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g=="; }; }; "make-dir-1.3.0" = { @@ -4658,13 +4649,13 @@ let sha512 = "pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw=="; }; }; - "map-age-cleaner-0.1.2" = { + "map-age-cleaner-0.1.3" = { name = "map-age-cleaner"; packageName = "map-age-cleaner"; - version = "0.1.2"; - src = fetchurl { - url = "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz"; - sha512 = "UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ=="; + version = "0.1.3"; + src = fetchurl { + url = "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz"; + sha512 = "bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w=="; }; }; "map-cache-0.2.2" = { @@ -5261,13 +5252,13 @@ let sha512 = "hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ=="; }; }; - "pako-1.0.6" = { + "pako-1.0.7" = { name = "pako"; packageName = "pako"; - version = "1.0.6"; - src = fetchurl { - url = "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz"; - sha512 = "lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg=="; + version = "1.0.7"; + src = fetchurl { + url = "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz"; + sha512 = "3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ=="; }; }; "parallel-transform-1.1.0" = { @@ -5711,13 +5702,13 @@ let sha1 = "b2c6a98c0072cf91b932d1a496508114311735bf"; }; }; - "postcss-modules-extract-imports-1.2.0" = { + "postcss-modules-extract-imports-1.2.1" = { name = "postcss-modules-extract-imports"; packageName = "postcss-modules-extract-imports"; - version = "1.2.0"; - src = fetchurl { - url = "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz"; - sha1 = "66140ecece38ef06bf0d3e355d69bf59d141ea85"; + version = "1.2.1"; + src = fetchurl { + url = "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz"; + sha512 = "6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw=="; }; }; "postcss-modules-local-by-default-1.2.0" = { @@ -6737,13 +6728,13 @@ let sha1 = "04e6926f662895354f3dd015203633b857297e2c"; }; }; - "sshpk-1.15.1" = { + "sshpk-1.15.2" = { name = "sshpk"; packageName = "sshpk"; - version = "1.15.1"; - src = fetchurl { - url = "https://registry.npmjs.org/sshpk/-/sshpk-1.15.1.tgz"; - sha512 = "mSdgNUaidk+dRU5MhYtN9zebdzF2iG0cNPWy8HG+W8y+fT1JnSkh0fzzpjOa0L7P8i1Rscz38t0h4gPcKz43xA=="; + version = "1.15.2"; + src = fetchurl { + url = "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz"; + sha512 = "Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA=="; }; }; "ssri-5.3.0" = { @@ -6863,6 +6854,15 @@ let sha512 = "n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="; }; }; + "string_decoder-1.2.0" = { + name = "string_decoder"; + packageName = "string_decoder"; + version = "1.2.0"; + src = fetchurl { + url = "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz"; + sha512 = "6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w=="; + }; + }; "stringstream-0.0.6" = { name = "stringstream"; packageName = "stringstream"; @@ -6971,22 +6971,22 @@ let sha1 = "9f5772413952135c6fefbf40afe6a4faa88b4bb5"; }; }; - "tapable-0.2.8" = { + "tapable-0.2.9" = { name = "tapable"; packageName = "tapable"; - version = "0.2.8"; - src = fetchurl { - url = "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz"; - sha1 = "99372a5c999bf2df160afc0d74bed4f47948cd22"; - }; - }; - "tapable-1.1.0" = { + version = "0.2.9"; + src = fetchurl { + url = "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz"; + sha512 = "2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A=="; + }; + }; + "tapable-1.1.1" = { name = "tapable"; packageName = "tapable"; - version = "1.1.0"; - src = fetchurl { - url = "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz"; - sha512 = "IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA=="; + version = "1.1.1"; + src = fetchurl { + url = "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz"; + sha512 = "9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA=="; }; }; "throttleit-1.0.0" = { @@ -7007,13 +7007,13 @@ let sha1 = "0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"; }; }; - "through2-2.0.3" = { + "through2-2.0.5" = { name = "through2"; packageName = "through2"; - version = "2.0.3"; - src = fetchurl { - url = "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz"; - sha1 = "0004569b37c7c74ba39c43f3ced78d1ad94140be"; + version = "2.0.5"; + src = fetchurl { + url = "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz"; + sha512 = "/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="; }; }; "timers-browserify-2.0.10" = { @@ -7439,13 +7439,13 @@ let sha512 = "1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw=="; }; }; - "v8flags-3.0.2" = { + "v8flags-3.1.1" = { name = "v8flags"; packageName = "v8flags"; - version = "3.0.2"; - src = fetchurl { - url = "https://registry.npmjs.org/v8flags/-/v8flags-3.0.2.tgz"; - sha512 = "6sgSKoFw1UpUPd3cFdF7QGnrH6tDeBgW1F3v9gy8gLY0mlbiBXq8soy8aQpY6xeeCjH5K+JvC62Acp7gtl7wWA=="; + version = "3.1.1"; + src = fetchurl { + url = "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz"; + sha512 = "iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ=="; }; }; "vendors-1.0.2" = { @@ -7628,15 +7628,6 @@ let sha1 = "b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"; }; }; - "xregexp-4.0.0" = { - name = "xregexp"; - packageName = "xregexp"; - version = "4.0.0"; - src = fetchurl { - url = "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz"; - sha512 = "PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg=="; - }; - }; "xtend-4.0.1" = { name = "xtend"; packageName = "xtend"; @@ -7664,13 +7655,13 @@ let sha1 = "1c11f9218f076089a47dd512f93c6699a6a81d52"; }; }; - "yargs-12.0.2" = { + "yargs-12.0.5" = { name = "yargs"; packageName = "yargs"; - version = "12.0.2"; - src = fetchurl { - url = "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz"; - sha512 = "e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ=="; + version = "12.0.5"; + src = fetchurl { + url = "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz"; + sha512 = "Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw=="; }; }; "yargs-3.10.0" = { @@ -7682,13 +7673,13 @@ let sha1 = "f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"; }; }; - "yargs-parser-10.1.0" = { + "yargs-parser-11.1.1" = { name = "yargs-parser"; packageName = "yargs-parser"; - version = "10.1.0"; - src = fetchurl { - url = "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz"; - sha512 = "VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ=="; + version = "11.1.1"; + src = fetchurl { + url = "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz"; + sha512 = "C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ=="; }; }; "yauzl-2.4.1" = { @@ -7708,7 +7699,7 @@ let src = ./..; dependencies = [ sources."@polymer/font-roboto-3.0.2" - sources."@polymer/iron-a11y-announcer-3.0.1" + sources."@polymer/iron-a11y-announcer-3.0.2" sources."@polymer/iron-a11y-keys-3.0.1" sources."@polymer/iron-a11y-keys-behavior-3.0.1" sources."@polymer/iron-ajax-3.0.1" @@ -7732,7 +7723,7 @@ let sources."@polymer/paper-tooltip-3.0.1" sources."@polymer/polymer-3.1.0" sources."@types/clone-0.1.30" - sources."@types/node-6.14.0" + sources."@types/node-6.14.2" sources."@types/parse5-2.2.34" sources."@webassemblyjs/ast-1.7.10" sources."@webassemblyjs/floating-point-hex-parser-1.7.10" @@ -7752,8 +7743,8 @@ let sources."@webassemblyjs/wasm-parser-1.7.10" sources."@webassemblyjs/wast-parser-1.7.10" sources."@webassemblyjs/wast-printer-1.7.10" - sources."@webcomponents/shadycss-1.5.2" - sources."@webcomponents/webcomponentsjs-2.1.3" + sources."@webcomponents/shadycss-1.6.0" + sources."@webcomponents/webcomponentsjs-2.2.1" sources."@xtuc/ieee754-1.2.0" sources."@xtuc/long-4.2.1" sources."abbrev-1.1.1" @@ -7927,7 +7918,7 @@ let sources."bcrypt-pbkdf-1.0.2" sources."big.js-3.2.0" sources."binary-extensions-1.12.0" - sources."bluebird-3.5.2" + sources."bluebird-3.5.3" sources."bn.js-4.11.8" sources."boolbase-1.0.0" sources."boom-2.10.1" @@ -7952,22 +7943,22 @@ let (sources."cacache-10.0.4" // { dependencies = [ sources."glob-7.1.3" - sources."graceful-fs-4.1.11" - sources."lru-cache-4.1.3" + sources."graceful-fs-4.1.15" + sources."lru-cache-4.1.5" sources."minimatch-3.0.4" sources."rimraf-2.6.2" ]; }) sources."cache-base-1.0.1" sources."camel-case-3.0.0" - sources."camelcase-4.1.0" + sources."camelcase-5.0.0" (sources."caniuse-api-1.6.1" // { dependencies = [ sources."browserslist-1.7.7" ]; }) - sources."caniuse-db-1.0.30000900" - sources."caniuse-lite-1.0.30000900" + sources."caniuse-db-1.0.30000912" + sources."caniuse-lite-1.0.30000912" sources."caseless-0.12.0" sources."center-align-0.1.3" sources."chalk-0.5.1" @@ -8017,7 +8008,7 @@ let sources."minimatch-3.0.4" ]; }) - sources."clipboard-2.0.1" + sources."clipboard-2.0.4" (sources."cliui-4.1.0" // { dependencies = [ sources."ansi-regex-3.0.0" @@ -8058,7 +8049,7 @@ let ]; }) sources."copy-descriptor-0.1.1" - (sources."copy-webpack-plugin-4.5.4" // { + (sources."copy-webpack-plugin-4.6.0" // { dependencies = [ sources."is-glob-4.0.0" sources."minimatch-3.0.4" @@ -8079,18 +8070,14 @@ let sources."css-color-names-0.0.4" sources."css-loader-0.28.11" sources."css-select-1.2.0" - (sources."css-selector-tokenizer-0.7.0" // { + (sources."css-selector-tokenizer-0.7.1" // { dependencies = [ sources."regexpu-core-1.0.0" ]; }) sources."css-what-2.1.2" sources."cssesc-0.1.0" - (sources."cssnano-3.10.0" // { - dependencies = [ - sources."decamelize-1.2.0" - ]; - }) + sources."cssnano-3.10.0" sources."csso-2.3.2" sources."cycle-1.0.3" sources."cyclist-0.2.2" @@ -8102,7 +8089,7 @@ let sources."date-now-0.1.4" sources."dateformat-1.0.2-1.2.3" sources."debug-2.6.9" - sources."decamelize-2.0.0" + sources."decamelize-1.2.0" sources."decode-uri-component-0.2.0" sources."deep-for-each-2.0.3" sources."define-properties-1.1.3" @@ -8139,13 +8126,13 @@ let ]; }) sources."ecc-jsbn-0.1.2" - sources."electron-to-chromium-1.3.82" + sources."electron-to-chromium-1.3.85" sources."elliptic-6.4.1" sources."emojis-list-2.1.0" sources."end-of-stream-1.4.1" (sources."enhanced-resolve-4.1.0" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" ]; }) sources."entities-1.0.0" @@ -8203,7 +8190,7 @@ let sources."eyes-0.1.8" sources."fast-deep-equal-2.0.1" sources."fast-json-stable-stringify-2.0.0" - sources."fastparse-1.1.1" + sources."fastparse-1.1.2" sources."favico.js-0.3.10" sources."faye-websocket-0.4.4" sources."fd-slicer-1.0.1" @@ -8244,12 +8231,12 @@ let }) (sources."fs-extra-1.0.0" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" ]; }) (sources."fs-write-stream-atomic-1.0.10" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" ]; }) sources."fs.realpath-1.0.0" @@ -8293,7 +8280,7 @@ let sources."good-listener-1.2.2" sources."graceful-fs-1.2.3" sources."grunt-0.4.5" - (sources."grunt-cli-1.3.1" // { + (sources."grunt-cli-1.3.2" // { dependencies = [ sources."nopt-4.0.1" ]; @@ -8477,7 +8464,7 @@ let sources."json5-0.5.1" (sources."jsonfile-2.4.0" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" ]; }) sources."jsonify-0.0.0" @@ -8490,14 +8477,14 @@ let sources."kind-of-6.0.2" (sources."klaw-1.3.1" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" ]; }) sources."lazy-cache-1.0.4" sources."lcid-2.0.0" (sources."less-2.7.3" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" ]; }) (sources."liftoff-2.5.0" // { @@ -8520,7 +8507,7 @@ let sources."lru-cache-2.7.3" sources."make-dir-1.3.0" sources."make-iterator-1.0.1" - sources."map-age-cleaner-0.1.2" + sources."map-age-cleaner-0.1.3" sources."map-cache-0.2.2" sources."map-visit-1.0.0" sources."math-expression-evaluator-1.2.17" @@ -8566,8 +8553,12 @@ let sources."no-case-2.3.2" (sources."node-libs-browser-2.1.0" // { dependencies = [ - sources."readable-stream-2.3.6" - sources."string_decoder-1.1.1" + (sources."readable-stream-2.3.6" // { + dependencies = [ + sources."string_decoder-1.1.1" + ]; + }) + sources."string_decoder-1.2.0" ]; }) sources."nopt-1.0.10" @@ -8616,7 +8607,7 @@ let sources."p-limit-1.3.0" sources."p-locate-2.0.0" sources."p-try-1.0.0" - sources."pako-1.0.6" + sources."pako-1.0.7" (sources."parallel-transform-1.1.0" // { dependencies = [ sources."readable-stream-2.3.6" @@ -8706,7 +8697,7 @@ let sources."postcss-minify-gradients-1.0.5" sources."postcss-minify-params-1.2.2" sources."postcss-minify-selectors-2.1.1" - (sources."postcss-modules-extract-imports-1.2.0" // { + (sources."postcss-modules-extract-imports-1.2.1" // { dependencies = [ sources."ansi-styles-3.2.1" sources."chalk-2.4.1" @@ -8783,7 +8774,7 @@ let }) (sources."readdirp-2.2.1" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" sources."readable-stream-2.3.6" sources."string_decoder-1.1.1" ]; @@ -8851,7 +8842,7 @@ let sources."sax-1.2.4" (sources."schema-utils-0.4.7" // { dependencies = [ - sources."ajv-6.5.4" + sources."ajv-6.6.1" ]; }) sources."select-1.1.2" @@ -8909,7 +8900,7 @@ let sources."split-1.0.1" sources."split-string-3.1.0" sources."sprintf-js-1.0.3" - (sources."sshpk-1.15.1" // { + (sources."sshpk-1.15.2" // { dependencies = [ sources."assert-plus-1.0.0" ]; @@ -8971,10 +8962,10 @@ let sources."js-yaml-3.7.0" ]; }) - sources."tapable-1.1.0" + sources."tapable-1.1.1" sources."throttleit-1.0.0" sources."through-2.3.8" - (sources."through2-2.0.3" // { + (sources."through2-2.0.5" // { dependencies = [ sources."readable-stream-2.3.6" sources."string_decoder-1.1.1" @@ -9004,9 +8995,9 @@ let dependencies = [ sources."colors-1.3.2" sources."enhanced-resolve-3.4.1" - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" sources."loader-utils-0.2.17" - sources."tapable-0.2.8" + sources."tapable-0.2.9" ]; }) sources."tslib-1.9.3" @@ -9075,7 +9066,7 @@ let sources."utila-0.4.0" sources."uuid-3.3.2" sources."v8-compile-cache-2.0.2" - sources."v8flags-3.0.2" + sources."v8flags-3.1.1" sources."vendors-1.0.2" (sources."verror-1.10.0" // { dependencies = [ @@ -9085,13 +9076,13 @@ let sources."vm-browserify-0.0.4" (sources."watchpack-1.6.0" // { dependencies = [ - sources."graceful-fs-4.1.11" + sources."graceful-fs-4.1.15" ]; }) sources."waypoints-4.0.1" (sources."webpack-4.23.1" // { dependencies = [ - sources."ajv-6.5.4" + sources."ajv-6.6.1" ]; }) (sources."webpack-cli-3.1.2" // { @@ -9119,7 +9110,6 @@ let sources."camelcase-1.2.1" sources."chalk-1.1.3" sources."cliui-2.1.0" - sources."decamelize-1.2.0" sources."has-ansi-2.0.0" sources."strip-ansi-3.0.1" sources."supports-color-2.0.0" @@ -9147,11 +9137,10 @@ let ]; }) sources."wrappy-1.0.2" - sources."xregexp-4.0.0" sources."xtend-4.0.1" sources."y18n-4.0.0" sources."yallist-2.1.2" - (sources."yargs-12.0.2" // { + (sources."yargs-12.0.5" // { dependencies = [ sources."find-up-3.0.0" sources."locate-path-3.0.0" @@ -9160,7 +9149,7 @@ let sources."p-try-2.0.0" ]; }) - sources."yargs-parser-10.1.0" + sources."yargs-parser-11.1.1" sources."yauzl-2.4.1" ]; buildInputs = globalBuildInputs; diff --git a/pkgs/python-packages-overrides.nix b/pkgs/python-packages-overrides.nix --- a/pkgs/python-packages-overrides.nix +++ b/pkgs/python-packages-overrides.nix @@ -72,7 +72,7 @@ self: super: { ]; }); - "nbconvert" = super."nbconvert".override (attrs: { + "nbconvert" = super."nbconvert".override (attrs: { propagatedBuildInputs = attrs.propagatedBuildInputs ++ [ # marcink: plug in jupyter-client for notebook rendering self."jupyter-client" @@ -120,10 +120,12 @@ self: super: { pkgs.curl pkgs.openssl ]; + preConfigure = '' substituteInPlace setup.py --replace '--static-libs' '--libs' export PYCURL_SSL_LIBRARY=openssl ''; + meta = { license = pkgs.lib.licenses.mit; }; @@ -168,14 +170,32 @@ self: super: { propagatedBuildInputs = [ pkgs.pam ]; + # TODO: johbo: Check if this can be avoided, or transform into # a real patch patchPhase = '' substituteInPlace pam.py \ --replace 'find_library("pam")' '"${pkgs.pam}/lib/libpam.so.0"' ''; + }); + "python-saml" = super."python-saml".override (attrs: { + buildInputs = [ + pkgs.libxml2 + pkgs.libxslt + ]; + }); + + "dm.xmlsec.binding" = super."dm.xmlsec.binding".override (attrs: { + buildInputs = [ + pkgs.libxml2 + pkgs.libxslt + pkgs.xmlsec + pkgs.libtool + ]; + }); + "pyzmq" = super."pyzmq".override (attrs: { buildInputs = [ pkgs.czmq diff --git a/pkgs/python-packages.nix b/pkgs/python-packages.nix --- a/pkgs/python-packages.nix +++ b/pkgs/python-packages.nix @@ -5,7 +5,7 @@ self: super: { "alembic" = super.buildPythonPackage { - name = "alembic-0.9.9"; + name = "alembic-1.0.5"; doCheck = false; propagatedBuildInputs = [ self."sqlalchemy" @@ -14,8 +14,8 @@ self: super: { self."python-dateutil" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/89/03/756d5b8e1c90bf283c3f435766aa3f20208d1c3887579dd8f2122e01d5f4/alembic-0.9.9.tar.gz"; - sha256 = "0bmkq6isjbmy4p7nxfvfpknjsx7rb3xn9g00169y891hcfkkxgc5"; + url = "https://files.pythonhosted.org/packages/1c/65/b8e4f5b2f345bb13b5e0a3fddd892b0b3f0e8ad4880e954fdc6a50d00d84/alembic-1.0.5.tar.gz"; + sha256 = "0rpjqp2iq6p49x1nli18ivak1izz547nnjxi110mzrgc1v7dxzz9"; }; meta = { license = [ pkgs.lib.licenses.mit ]; @@ -36,7 +36,7 @@ self: super: { }; }; "appenlight-client" = super.buildPythonPackage { - name = "appenlight-client-0.6.25"; + name = "appenlight-client-0.6.26"; doCheck = false; propagatedBuildInputs = [ self."webob" @@ -44,8 +44,8 @@ self: super: { self."six" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/fa/44/2911ef85ea4f4fe65058fd22959d8dad598fab6a3c84e5bcb569d15c8783/appenlight_client-0.6.25.tar.gz"; - sha256 = "1r9l2rfg677nxhamdbyb9y4fs1zgy2dy1p19c68fnvqkxz40y627"; + url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz"; + sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712"; }; meta = { license = [ pkgs.lib.licenses.bsdOriginal ]; @@ -146,15 +146,15 @@ self: super: { }; }; "bleach" = super.buildPythonPackage { - name = "bleach-2.1.4"; + name = "bleach-3.0.2"; doCheck = false; propagatedBuildInputs = [ self."six" - self."html5lib" + self."webencodings" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/7a/b7/fa555afb61462b030abaf9ed1479b8ea031510f58c7706b06113be9f82ea/bleach-2.1.4.tar.gz"; - sha256 = "1n337zbdml6z6zia0b1qgv6xiddx3qlwmcg9vk2mk60jcxhmzs8f"; + url = "https://files.pythonhosted.org/packages/ae/31/680afc7d44040004296a2d8f0584983c2f2386448cd9d0964197e6c1160e/bleach-3.0.2.tar.gz"; + sha256 = "06474zg7f73hv8h1xw2wcsmvn2ygj73zxgxxqg8zcx8ap1srdls8"; }; meta = { license = [ pkgs.lib.licenses.asl20 ]; @@ -230,15 +230,16 @@ self: super: { }; }; "colander" = super.buildPythonPackage { - name = "colander-1.4"; + name = "colander-1.5.1"; doCheck = false; propagatedBuildInputs = [ self."translationstring" self."iso8601" + self."enum34" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/cc/e2/c4e716ac4a426d8ad4dfe306c34f0018a22275d2420815784005bf771c84/colander-1.4.tar.gz"; - sha256 = "0wjfphyr5aakv5hw73q287lbc15cbm0aardajv7i2mqf377rl3p2"; + url = "https://files.pythonhosted.org/packages/ec/d1/fcca811a0a692c69d27e36b4d11a73acb98b4bab48323442642b6fd4386d/colander-1.5.1.tar.gz"; + sha256 = "18ah4cwwxnpm6qxi6x9ipy51dal4spd343h44s5wd01cnhgrwsyq"; }; meta = { license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; @@ -317,7 +318,7 @@ self: super: { }; }; "deform" = super.buildPythonPackage { - name = "deform-2.0.5"; + name = "deform-2.0.7"; doCheck = false; propagatedBuildInputs = [ self."chameleon" @@ -328,11 +329,37 @@ self: super: { self."zope.deprecation" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/0c/b1/ba711d5808c12538c8504f534d79c124ed834f19ac36f0ac5391c3bbd1c1/deform-2.0.5.tar.gz"; - sha256 = "0ybg9zsnfac1kaxrjanmkjk0xaklf4d3piywxwr08l1cl1336kc7"; + url = "https://files.pythonhosted.org/packages/cf/a1/bc234527b8f181de9acd80e796483c00007658d1e32b7de78f1c2e004d9a/deform-2.0.7.tar.gz"; + sha256 = "0jnpi0zr2hjvbmiz6nm33yqv976dn9lf51vhlzqc0i75xcr9rwig"; + }; + meta = { + license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; + }; + }; + "defusedxml" = super.buildPythonPackage { + name = "defusedxml-0.5.0"; + doCheck = false; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/74/ba/4ba4e89e21b5a2e267d80736ea674609a0a33cc4435a6d748ef04f1f9374/defusedxml-0.5.0.tar.gz"; + sha256 = "1x54n0h8hl92vvwyymx883fbqpqjwn2mc8fb383bcg3z9zwz5mr4"; }; meta = { - license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; + license = [ pkgs.lib.licenses.psfl ]; + }; + }; + "dm.xmlsec.binding" = super.buildPythonPackage { + name = "dm.xmlsec.binding-1.3.7"; + doCheck = false; + propagatedBuildInputs = [ + self."setuptools" + self."lxml" + ]; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz"; + sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3"; + }; + meta = { + license = [ pkgs.lib.licenses.bsdOriginal ]; }; }; "docutils" = super.buildPythonPackage { @@ -490,14 +517,14 @@ self: super: { }; }; "gevent" = super.buildPythonPackage { - name = "gevent-1.3.6"; + name = "gevent-1.3.7"; doCheck = false; propagatedBuildInputs = [ self."greenlet" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/49/13/aa4bb3640b5167fe58875d3d7e65390cdb14f9682a41a741a566bb560842/gevent-1.3.6.tar.gz"; - sha256 = "1ih4k73dqz2zb561hda99vbanja3m6cdch3mgxxn1mla3qwkqhbv"; + url = "https://files.pythonhosted.org/packages/10/c1/9499b146bfa43aa4f1e0ed1bab1bd3209a4861d25650c11725036c731cf5/gevent-1.3.7.tar.gz"; + sha256 = "0b0fr04qdk1p4sniv87fh8z5psac60x01pv054kpgi94520g81iz"; }; meta = { license = [ pkgs.lib.licenses.mit ]; @@ -547,27 +574,12 @@ self: super: { license = [ pkgs.lib.licenses.mit ]; }; }; - "html5lib" = super.buildPythonPackage { - name = "html5lib-1.0.1"; - doCheck = false; - propagatedBuildInputs = [ - self."six" - self."webencodings" - ]; - src = fetchurl { - url = "https://files.pythonhosted.org/packages/85/3e/cf449cf1b5004e87510b9368e7a5f1acd8831c2d6691edd3c62a0823f98f/html5lib-1.0.1.tar.gz"; - sha256 = "0dipzfrycv6j1jw82v9b7d8lzggx3x8xngx6l4xrqkxwvg7hvjv6"; - }; - meta = { - license = [ pkgs.lib.licenses.mit ]; - }; - }; "hupper" = super.buildPythonPackage { - name = "hupper-1.3.1"; + name = "hupper-1.4.2"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/cf/4b/467b826a84c8594b81f414b5ab6794e981951dac90ca40abaf9ea1cb36b0/hupper-1.3.1.tar.gz"; - sha256 = "03mf13n6i4dd60wlb9m99ddl4m3lmly70cjp7f82vdkibfl1v6l9"; + url = "https://files.pythonhosted.org/packages/f1/75/1915dc7650b4867fa3049256e24ca8eddb5989998fcec788cf52b9812dfc/hupper-1.4.2.tar.gz"; + sha256 = "16vb9fkiaakdpcp6pn56h3w0dwvm67bxq2k2dv4i382qhqwphdzb"; }; meta = { license = [ pkgs.lib.licenses.mit ]; @@ -671,6 +683,20 @@ self: super: { license = [ pkgs.lib.licenses.mit ]; }; }; + "isodate" = super.buildPythonPackage { + name = "isodate-0.6.0"; + doCheck = false; + propagatedBuildInputs = [ + self."six" + ]; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz"; + sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif"; + }; + meta = { + license = [ pkgs.lib.licenses.bsdOriginal ]; + }; + }; "itsdangerous" = super.buildPythonPackage { name = "itsdangerous-0.24"; doCheck = false; @@ -756,11 +782,11 @@ self: super: { }; }; "lxml" = super.buildPythonPackage { - name = "lxml-3.7.3"; + name = "lxml-4.2.5"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/39/e8/a8e0b1fa65dd021d48fe21464f71783655f39a41f218293c1c590d54eb82/lxml-3.7.3.tar.gz"; - sha256 = "1iv1jgkqn1hdh1xyxri6g0y1s67h01jzjkw2nhkx3rqylmw2sl5a"; + url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz"; + sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin"; }; meta = { license = [ pkgs.lib.licenses.bsdOriginal ]; @@ -990,11 +1016,11 @@ self: super: { }; }; "peppercorn" = super.buildPythonPackage { - name = "peppercorn-0.5"; + name = "peppercorn-0.6"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/45/ec/a62ec317d1324a01567c5221b420742f094f05ee48097e5157d32be3755c/peppercorn-0.5.tar.gz"; - sha256 = "0jvp144zn7yqk9kbpxc059167mlqk85i5lpvl1niw8gsa5fvl74j"; + url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz"; + sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn"; }; meta = { license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ]; @@ -1139,15 +1165,15 @@ self: super: { }; }; "py-gfm" = super.buildPythonPackage { - name = "py-gfm-0.1.3"; + name = "py-gfm-0.1.4"; doCheck = false; propagatedBuildInputs = [ self."setuptools" self."markdown" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/12/e4/6b3d8678da04f97d7490d8264d8de51c2dc9fb91209ccee9c515c95e14c5/py-gfm-0.1.3.tar.gz"; - sha256 = "162ggwwj0af9g3s1k8m4bfwbvis03x9pinnf35mj79pb90rf81zi"; + url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz"; + sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg"; }; meta = { license = [ pkgs.lib.licenses.bsdOriginal ]; @@ -1212,11 +1238,11 @@ self: super: { }; }; "pygments" = super.buildPythonPackage { - name = "pygments-2.2.0"; + name = "pygments-2.3.0"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/71/2a/2e4e77803a8bd6408a2903340ac498cb0a2181811af7c9ec92cb70b0308a/Pygments-2.2.0.tar.gz"; - sha256 = "1k78qdvir1yb1c634nkv6rbga8wv4289xarghmsbbvzhvr311bnv"; + url = "https://files.pythonhosted.org/packages/63/a2/91c31c4831853dedca2a08a0f94d788fc26a48f7281c99a303769ad2721b/Pygments-2.3.0.tar.gz"; + sha256 = "1z34ms51dh4jq4h3cizp7vd1dmsxcbvffkjsd2xxfav22nn6lrl2"; }; meta = { license = [ pkgs.lib.licenses.bsdOriginal ]; @@ -1248,14 +1274,14 @@ self: super: { }; }; "pyotp" = super.buildPythonPackage { - name = "pyotp-2.2.6"; + name = "pyotp-2.2.7"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/67/69/131f5ad63de40c30f3be88d891e4a2ea1b69398528db99bc1e5c543422fa/pyotp-2.2.6.tar.gz"; - sha256 = "0sdxxvr3j4j0pk26v258jpxhgpbnpmyqhvzhl24hsd50j7fk14fx"; + url = "https://files.pythonhosted.org/packages/b1/ab/477cda97b6ca7baced5106471cb1ac1fe698d1b035983b9f8ee3422989eb/pyotp-2.2.7.tar.gz"; + sha256 = "00p69nw431f0s2ilg0hnd77p1l22m06p9rq4f8zfapmavnmzw3xy"; }; meta = { - license = [ pkgs.lib.licenses.bsdOriginal ]; + license = [ pkgs.lib.licenses.mit ]; }; }; "pyparsing" = super.buildPythonPackage { @@ -1479,14 +1505,14 @@ self: super: { }; }; "python-dateutil" = super.buildPythonPackage { - name = "python-dateutil-2.7.3"; + name = "python-dateutil-2.7.5"; doCheck = false; propagatedBuildInputs = [ self."six" ]; src = fetchurl { - url = "https://files.pythonhosted.org/packages/a0/b0/a4e3241d2dee665fea11baec21389aec6886655cd4db7647ddf96c3fad15/python-dateutil-2.7.3.tar.gz"; - sha256 = "1f7h54lg0w2ckch7592xpjkh8dg87k2br256h0iw49zn6bg02w72"; + url = "https://files.pythonhosted.org/packages/0e/01/68747933e8d12263d41ce08119620d9a7e5eb72c876a3442257f74490da0/python-dateutil-2.7.5.tar.gz"; + sha256 = "00ngwcdw36w5b37b51mdwn3qxid9zdf3kpffv2q6n9kl05y2iyc8"; }; meta = { license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ]; @@ -1543,6 +1569,22 @@ self: super: { license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ]; }; }; + "python-saml" = super.buildPythonPackage { + name = "python-saml-2.4.2"; + doCheck = false; + propagatedBuildInputs = [ + self."dm.xmlsec.binding" + self."isodate" + self."defusedxml" + ]; + src = fetchurl { + url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz"; + sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43"; + }; + meta = { + license = [ pkgs.lib.licenses.mit ]; + }; + }; "pytz" = super.buildPythonPackage { name = "pytz-2018.4"; doCheck = false; @@ -1615,7 +1657,7 @@ self: super: { }; }; "rhodecode-enterprise-ce" = super.buildPythonPackage { - name = "rhodecode-enterprise-ce-4.14.1"; + name = "rhodecode-enterprise-ce-4.15.0"; buildInputs = [ self."pytest" self."py" @@ -1640,6 +1682,7 @@ self: super: { self."attrs" self."babel" self."beaker" + self."bleach" self."celery" self."chameleon" self."channelstream" @@ -1668,8 +1711,6 @@ self: super: { self."markdown" self."markupsafe" self."msgpack-python" - self."mysql-python" - self."pymysql" self."pyotp" self."packaging" self."paste" @@ -1678,7 +1719,6 @@ self: super: { self."pathlib2" self."peppercorn" self."psutil" - self."psycopg2" self."py-bcrypt" self."pycrypto" self."pycurl" @@ -1692,11 +1732,11 @@ self: super: { self."pyramid-mako" self."pyramid" self."pyramid-mailer" - self."pysqlite" self."python-dateutil" self."python-ldap" self."python-memcached" self."python-pam" + self."python-saml" self."pytz" self."tzlocal" self."pyzmq" @@ -1713,7 +1753,6 @@ self: super: { self."supervisor" self."tempita" self."translationstring" - self."trollius" self."urllib3" self."urlobject" self."venusian" @@ -1727,8 +1766,11 @@ self: super: { self."zope.deprecation" self."zope.event" self."zope.interface" + self."mysql-python" + self."pymysql" + self."pysqlite" + self."psycopg2" self."nbconvert" - self."bleach" self."nbformat" self."jupyter-client" self."alembic" @@ -1822,11 +1864,11 @@ self: super: { }; }; "setuptools" = super.buildPythonPackage { - name = "setuptools-40.4.3"; + name = "setuptools-40.6.2"; doCheck = false; src = fetchurl { - url = "https://files.pythonhosted.org/packages/6e/9c/6a003320b00ef237f94aa74e4ad66c57a7618f6c79d67527136e2544b728/setuptools-40.4.3.zip"; - sha256 = "058v6zns4634n4al2nmmvp15j8nrgwn8wjrbdks47wk3vm05gg5c"; + url = "https://files.pythonhosted.org/packages/b0/d1/8acb42f391cba52e35b131e442e80deffbb8d0676b93261d761b1f0ef8fb/setuptools-40.6.2.zip"; + sha256 = "0r2c5hapirlzm34h7pl1lgkm6gk7bcrlrdj28qgsvaqg3f74vfw6"; }; meta = { license = [ pkgs.lib.licenses.mit ]; @@ -2002,20 +2044,6 @@ self: super: { license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ]; }; }; - "trollius" = super.buildPythonPackage { - name = "trollius-1.0.4"; - doCheck = false; - propagatedBuildInputs = [ - self."futures" - ]; - src = fetchurl { - url = "https://files.pythonhosted.org/packages/aa/e6/4141db437f55e6ee7a3fb69663239e3fde7841a811b4bef293145ad6c836/trollius-1.0.4.tar.gz"; - sha256 = "0xny8y12x3wrflmyn6xi8a7n3m3ac80fgmgzphx5jbbaxkjcm148"; - }; - meta = { - license = [ pkgs.lib.licenses.asl20 ]; - }; - }; "tzlocal" = super.buildPythonPackage { name = "tzlocal-1.5.1"; doCheck = false; diff --git a/pkgs/shell-generate.nix b/pkgs/shell-generate.nix --- a/pkgs/shell-generate.nix +++ b/pkgs/shell-generate.nix @@ -32,6 +32,11 @@ pkgs.stdenv.mkDerivation { # We need postgresql to be around pkgs.postgresql + # we need the below for saml + pkgs.libxml2 + pkgs.libxslt + pkgs.xmlsec + # Curl is needed for pycurl pkgs.curl ]; diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -8,16 +8,17 @@ atomicwrites==1.2.1 attrs==18.2.0 babel==1.3 beaker==1.9.1 +bleach==3.0.2 celery==4.1.1 chameleon==2.24 channelstream==0.5.2 click==6.6 -colander==1.4.0 +colander==1.5.1 # our custom configobj https://code.rhodecode.com/upstream/configobj/archive/a11ff0a0bd4fbda9e3a91267e720f88329efb4a6.tar.gz?md5=9916c524ea11a6c418217af6b28d4b3c#egg=configobj==5.0.6 cssselect==1.0.3 decorator==4.1.2 -deform==2.0.5 +deform==2.0.7 docutils==0.14.0 dogpile.cache==0.6.7 dogpile.core==0.4.1 @@ -32,28 +33,25 @@ itsdangerous==0.24 jinja2==2.9.6 billiard==3.5.0.3 kombu==4.2.0 -lxml==3.7.3 +lxml==4.2.5 mako==1.0.7 markdown==2.6.11 markupsafe==1.0.0 msgpack-python==0.5.6 -mysql-python==1.2.5 -pymysql==0.8.1 -pyotp==2.2.6 +pyotp==2.2.7 packaging==15.2 paste==2.0.3 pastedeploy==1.5.2 pastescript==2.0.2 pathlib2==2.3.2 -peppercorn==0.5 +peppercorn==0.6 psutil==5.4.7 -psycopg2==2.7.5 py-bcrypt==0.4 pycrypto==2.6.1 pycurl==7.43.0.2 pyflakes==0.8.1 pygments-markdown-lexer==0.1.0.dev39 -pygments==2.2.0 +pygments==2.3.0 pyparsing==1.5.7 pyramid-beaker==0.8 pyramid-debugtoolbar==4.4.0 @@ -61,15 +59,15 @@ pyramid-jinja2==2.7 pyramid-mako==1.0.2 pyramid==1.9.2 pyramid_mailer==0.15.1 -pysqlite==2.8.3 python-dateutil python-ldap==3.1.0 python-memcached==1.59 python-pam==1.8.4 +python-saml pytz==2018.4 tzlocal==1.5.1 pyzmq==14.6.0 -py-gfm==0.1.3 +py-gfm==0.1.4 redis==2.10.6 repoze.lru==0.7 requests==2.9.1 @@ -82,7 +80,6 @@ subprocess32==3.5.2 supervisor==3.3.4 tempita==0.5.2 translationstring==1.3 -trollius==1.0.4 urllib3==1.21 urlobject==2.4.3 venusian==1.1.0 @@ -97,22 +94,26 @@ zope.deprecation==4.3.0 zope.event==4.3.0 zope.interface==4.5.0 +# DB drivers +mysql-python==1.2.5 +pymysql==0.8.1 +pysqlite==2.8.3 +psycopg2==2.7.5 # IPYTHON RENDERING # entrypoints backport, pypi version doesn't support egg installs https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1 nbconvert==5.3.1 -bleach==2.1.4 nbformat==4.4.0 jupyter_client==5.0.0 ## cli tools -alembic==0.9.9 +alembic==1.0.5 invoke==0.13.0 bumpversion==0.5.3 ## http servers -gevent==1.3.6 +gevent==1.3.7 greenlet==0.4.15 gunicorn==19.9.0 waitress==1.1.0 @@ -126,7 +127,7 @@ ipython==5.1.0 https://code.rhodecode.com/rhodecode-tools-ce/archive/v1.0.1.tar.gz?md5=ffb5d6bcb855305b93cfe23ad42e500b#egg=rhodecode-tools==1.0.1 ## appenlight -appenlight-client==0.6.25 +appenlight-client==0.6.26 ## test related requirements -r requirements_test.txt diff --git a/rhodecode/VERSION b/rhodecode/VERSION --- a/rhodecode/VERSION +++ b/rhodecode/VERSION @@ -1,1 +1,1 @@ -4.14.1 \ No newline at end of file +4.15.0 \ No newline at end of file diff --git a/rhodecode/api/tests/test_create_pull_request.py b/rhodecode/api/tests/test_create_pull_request.py --- a/rhodecode/api/tests/test_create_pull_request.py +++ b/rhodecode/api/tests/test_create_pull_request.py @@ -66,7 +66,7 @@ class TestCreatePullRequestApi(object): expected_message = "Created new pull request `{title}`".format( title=data['title']) result = response.json - assert result['error'] == None + assert result['error'] is None assert result['result']['msg'] == expected_message pull_request_id = result['result']['pull_request_id'] pull_request = PullRequestModel().get(pull_request_id) @@ -89,7 +89,7 @@ class TestCreatePullRequestApi(object): expected_message = "Created new pull request `{title}`".format( title=data['title']) result = response.json - assert result['error'] == None + assert result['error'] is None assert result['result']['msg'] == expected_message pull_request_id = result['result']['pull_request_id'] pull_request = PullRequestModel().get(pull_request_id) @@ -129,7 +129,7 @@ class TestCreatePullRequestApi(object): expected_message = "Created new pull request `{title}`".format( title=data['title']) result = response.json - assert result['error'] == None + assert result['error'] is None assert result['result']['msg'] == expected_message pull_request_id = result['result']['pull_request_id'] pull_request = PullRequestModel().get(pull_request_id) @@ -144,11 +144,15 @@ class TestCreatePullRequestApi(object): entry['mandatory'] = rev.mandatory actual_reviewers.append(entry) - # default reviewer will be added who is an owner of the repo - reviewers.append( - {'username': pull_request.author.username, - 'reasons': [u'Default reviewer', u'Repository owner']}, - ) + owner_username = pull_request.target_repo.user.username + for spec_reviewer in reviewers[::]: + # default reviewer will be added who is an owner of the repo + # this get's overridden by a add owner to reviewers rule + if spec_reviewer['username'] == owner_username: + spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner'] + # since owner is more important, we don't inherit mandatory flag + del spec_reviewer['mandatory'] + assert sorted(actual_reviewers, key=lambda e: e['username']) \ == sorted(reviewers, key=lambda e: e['username']) @@ -173,7 +177,7 @@ class TestCreatePullRequestApi(object): expected_message = "Created new pull request `{title}`".format( title=data['title']) result = response.json - assert result['error'] == None + assert result['error'] is None assert result['result']['msg'] == expected_message pull_request_id = result['result']['pull_request_id'] pull_request = PullRequestModel().get(pull_request_id) @@ -187,11 +191,14 @@ class TestCreatePullRequestApi(object): if rev.mandatory: entry['mandatory'] = rev.mandatory actual_reviewers.append(entry) - # default reviewer will be added who is an owner of the repo - reviewers.append( - {'username': pull_request.author.user_id, - 'reasons': [u'Default reviewer', u'Repository owner']}, - ) + + owner_user_id = pull_request.target_repo.user.user_id + for spec_reviewer in reviewers[::]: + # default reviewer will be added who is an owner of the repo + # this get's overridden by a add owner to reviewers rule + if spec_reviewer['username'] == owner_user_id: + spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner'] + assert sorted(actual_reviewers, key=lambda e: e['username']) \ == sorted(reviewers, key=lambda e: e['username']) diff --git a/rhodecode/apps/admin/interfaces.py b/rhodecode/apps/_base/interfaces.py rename from rhodecode/apps/admin/interfaces.py rename to rhodecode/apps/_base/interfaces.py diff --git a/rhodecode/apps/admin/navigation.py b/rhodecode/apps/_base/navigation.py rename from rhodecode/apps/admin/navigation.py rename to rhodecode/apps/_base/navigation.py --- a/rhodecode/apps/admin/navigation.py +++ b/rhodecode/apps/_base/navigation.py @@ -24,7 +24,7 @@ import collections from zope.interface import implementer -from rhodecode.apps.admin.interfaces import IAdminNavigationRegistry +from rhodecode.apps._base.interfaces import IAdminNavigationRegistry from rhodecode.lib.utils2 import str2bool from rhodecode.translation import _ @@ -117,10 +117,11 @@ class NavigationRegistry(object): self._registered_entries[entry.key] = entry def get_navlist(self, request): - navlist = [NavListEntry(i.key, i.get_localized_name(request), - i.generate_url(request), i.active_list) - for i in self._registered_entries.values()] - return navlist + nav_list = [ + NavListEntry(i.key, i.get_localized_name(request), + i.generate_url(request), i.active_list) + for i in self._registered_entries.values()] + return nav_list def navigation_registry(request, registry=None): @@ -143,5 +144,5 @@ def includeme(config): # Create admin navigation registry and add it to the pyramid registry. settings = config.get_settings() labs_active = str2bool(settings.get('labs_settings_active', False)) - navigation_registry = NavigationRegistry(labs_active=labs_active) - config.registry.registerUtility(navigation_registry) \ No newline at end of file + navigation_registry_instance = NavigationRegistry(labs_active=labs_active) + config.registry.registerUtility(navigation_registry_instance) diff --git a/rhodecode/apps/admin/subscribers.py b/rhodecode/apps/_base/subscribers.py rename from rhodecode/apps/admin/subscribers.py rename to rhodecode/apps/_base/subscribers.py diff --git a/rhodecode/apps/admin/__init__.py b/rhodecode/apps/admin/__init__.py --- a/rhodecode/apps/admin/__init__.py +++ b/rhodecode/apps/admin/__init__.py @@ -429,7 +429,7 @@ def admin_routes(config): def includeme(config): - from rhodecode.apps.admin.navigation import includeme as nav_includeme + from rhodecode.apps._base.navigation import includeme as nav_includeme # Create admin navigation registry and add it to the pyramid registry. nav_includeme(config) @@ -438,7 +438,5 @@ def includeme(config): config.add_route(name='admin_home', pattern=ADMIN_PREFIX) config.include(admin_routes, route_prefix=ADMIN_PREFIX) - config.include('.subscribers') - # Scan module for configuration decorators. config.scan('.views', ignore='.tests') diff --git a/rhodecode/apps/admin/tests/test_admin_auth_settings.py b/rhodecode/apps/admin/tests/test_admin_auth_settings.py --- a/rhodecode/apps/admin/tests/test_admin_auth_settings.py +++ b/rhodecode/apps/admin/tests/test_admin_auth_settings.py @@ -89,7 +89,7 @@ class TestAuthSettingsView(object): 'timeout': 3600, 'tls_kind': 'PLAIN', 'tls_reqcert': 'NEVER', - + 'tls_cert_dir':'/etc/openldap/cacerts', 'dn_user': 'test_user', 'dn_pass': 'test_pass', 'base_dn': 'test_base_dn', diff --git a/rhodecode/apps/admin/views/exception_tracker.py b/rhodecode/apps/admin/views/exception_tracker.py --- a/rhodecode/apps/admin/views/exception_tracker.py +++ b/rhodecode/apps/admin/views/exception_tracker.py @@ -24,7 +24,7 @@ from pyramid.httpexceptions import HTTPF from pyramid.view import view_config from rhodecode.apps._base import BaseAppView -from rhodecode.apps.admin.navigation import navigation_list +from rhodecode.apps._base.navigation import navigation_list from rhodecode.lib import helpers as h from rhodecode.lib.auth import ( LoginRequired, HasPermissionAllDecorator, CSRFRequired) diff --git a/rhodecode/apps/admin/views/open_source_licenses.py b/rhodecode/apps/admin/views/open_source_licenses.py --- a/rhodecode/apps/admin/views/open_source_licenses.py +++ b/rhodecode/apps/admin/views/open_source_licenses.py @@ -24,7 +24,7 @@ import logging from pyramid.view import view_config from rhodecode.apps._base import BaseAppView -from rhodecode.apps.admin.navigation import navigation_list +from rhodecode.apps._base.navigation import navigation_list from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator) from rhodecode.lib.utils import read_opensource_licenses diff --git a/rhodecode/apps/admin/views/process_management.py b/rhodecode/apps/admin/views/process_management.py --- a/rhodecode/apps/admin/views/process_management.py +++ b/rhodecode/apps/admin/views/process_management.py @@ -25,7 +25,7 @@ import signal from pyramid.view import view_config from rhodecode.apps._base import BaseAppView -from rhodecode.apps.admin.navigation import navigation_list +from rhodecode.apps._base.navigation import navigation_list from rhodecode.lib import system_info from rhodecode.lib.auth import ( LoginRequired, HasPermissionAllDecorator, CSRFRequired) diff --git a/rhodecode/apps/admin/views/sessions.py b/rhodecode/apps/admin/views/sessions.py --- a/rhodecode/apps/admin/views/sessions.py +++ b/rhodecode/apps/admin/views/sessions.py @@ -24,7 +24,7 @@ from pyramid.view import view_config from pyramid.httpexceptions import HTTPFound from rhodecode.apps._base import BaseAppView -from rhodecode.apps.admin.navigation import navigation_list +from rhodecode.apps._base.navigation import navigation_list from rhodecode.lib.auth import ( LoginRequired, HasPermissionAllDecorator, CSRFRequired) from rhodecode.lib.utils2 import safe_int diff --git a/rhodecode/apps/admin/views/settings.py b/rhodecode/apps/admin/views/settings.py --- a/rhodecode/apps/admin/views/settings.py +++ b/rhodecode/apps/admin/views/settings.py @@ -33,7 +33,7 @@ from pyramid.renderers import render from pyramid.response import Response from rhodecode.apps._base import BaseAppView -from rhodecode.apps.admin.navigation import navigation_list +from rhodecode.apps._base.navigation import navigation_list from rhodecode.apps.svn_support.config_keys import generate_config from rhodecode.lib import helpers as h from rhodecode.lib.auth import ( @@ -175,8 +175,7 @@ class AdminSettingsView(BaseAppView): try: if c.visual.allow_repo_location_change: - model.update_global_path_setting( - form_result['paths_root_path']) + model.update_global_path_setting(form_result['paths_root_path']) model.update_global_ssl_setting(form_result['web_push_ssl']) model.update_global_hook_settings(form_result) diff --git a/rhodecode/apps/admin/views/system_info.py b/rhodecode/apps/admin/views/system_info.py --- a/rhodecode/apps/admin/views/system_info.py +++ b/rhodecode/apps/admin/views/system_info.py @@ -25,7 +25,7 @@ from pyramid.view import view_config import rhodecode from rhodecode.apps._base import BaseAppView -from rhodecode.apps.admin.navigation import navigation_list +from rhodecode.apps._base.navigation import navigation_list from rhodecode.lib import helpers as h from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator) from rhodecode.lib.utils2 import str2bool @@ -96,6 +96,7 @@ class AdminSystemInfoSettingsView(BaseAp # RhodeCode specific (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')), (_('Latest version'), version, update_state), + (_('RhodeCode Base URL'), val('rhodecode_config')['config'].get('app.base_url'), state('rhodecode_config')), (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')), (_('RhodeCode Server ID'), val('server')['server_id'], state('server')), (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')), diff --git a/rhodecode/apps/admin/views/users.py b/rhodecode/apps/admin/views/users.py --- a/rhodecode/apps/admin/views/users.py +++ b/rhodecode/apps/admin/views/users.py @@ -185,7 +185,7 @@ class AdminUsersView(BaseAppView, DataGr def users_new(self): _ = self.request.translate c = self.load_default_context() - c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name + c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid self._set_personal_repo_group_template_vars(c) return self._get_template_context(c) @@ -198,7 +198,7 @@ class AdminUsersView(BaseAppView, DataGr def users_create(self): _ = self.request.translate c = self.load_default_context() - c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name + c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid user_model = UserModel() user_form = UserForm(self.request.translate)() try: diff --git a/rhodecode/apps/channelstream/__init__.py b/rhodecode/apps/channelstream/__init__.py --- a/rhodecode/apps/channelstream/__init__.py +++ b/rhodecode/apps/channelstream/__init__.py @@ -63,7 +63,7 @@ def maybe_create_history_store(event): settings = event.app.registry.settings history_dir = settings.get('channelstream.history.location', '') if history_dir and not os.path.exists(history_dir): - os.makedirs(history_dir, 0750) + os.makedirs(history_dir, 0o750) def includeme(config): diff --git a/rhodecode/apps/login/views.py b/rhodecode/apps/login/views.py --- a/rhodecode/apps/login/views.py +++ b/rhodecode/apps/login/views.py @@ -32,6 +32,7 @@ from pyramid.view import view_config from rhodecode.apps._base import BaseAppView from rhodecode.authentication.base import authenticate, HTTP_TYPE +from rhodecode.authentication.plugins import auth_rhodecode from rhodecode.events import UserRegistered, trigger from rhodecode.lib import helpers as h from rhodecode.lib import audit_logger @@ -55,7 +56,7 @@ CaptchaData = collections.namedtuple( 'CaptchaData', 'active, private_key, public_key') -def _store_user_in_session(session, username, remember=False): +def store_user_in_session(session, username, remember=False): user = User.get_by_username(username, case_insensitive=True) auth_user = AuthUser(user.user_id) auth_user.set_authenticated() @@ -165,7 +166,7 @@ class LoginView(BaseAppView): auth_info = authenticate( '', '', self.request.environ, HTTP_TYPE, skip_missing=True) if auth_info: - headers = _store_user_in_session( + headers = store_user_in_session( self.session, auth_info.get('username')) raise HTTPFound(c.came_from, headers=headers) except UserCreationError as e: @@ -186,7 +187,7 @@ class LoginView(BaseAppView): self.session.invalidate() form_result = login_form.to_python(self.request.POST) # form checks for username/password, now we're authenticated - headers = _store_user_in_session( + headers = store_user_in_session( self.session, username=form_result['username'], remember=form_result['remember']) @@ -273,16 +274,26 @@ class LoginView(BaseAppView): route_name='register', request_method='POST', renderer='rhodecode:templates/register.mako') def register_post(self): + from rhodecode.authentication.plugins import auth_rhodecode + self.load_default_context() captcha = self._get_captcha_data() auto_active = 'hg.register.auto_activate' in User.get_default_user()\ .AuthUser().permissions['global'] + extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid + extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid + register_form = RegisterForm(self.request.translate)() try: form_result = register_form.to_python(self.request.POST) form_result['active'] = auto_active + external_identity = self.request.POST.get('external_identity') + + if external_identity: + extern_name = external_identity + extern_type = external_identity if captcha.active: captcha_status, captcha_message = self.validate_captcha( @@ -295,11 +306,17 @@ class LoginView(BaseAppView): raise formencode.Invalid( _msg, _value, None, error_dict=error_dict) - new_user = UserModel().create_registration(form_result) + new_user = UserModel().create_registration( + form_result, extern_name=extern_name, extern_type=extern_type) action_data = {'data': new_user.get_api_data(), 'user_agent': self.request.user_agent} + + + if external_identity: + action_data['external_identity'] = external_identity + audit_user = audit_logger.UserWrap( username=new_user.username, user_id=new_user.user_id, @@ -351,15 +368,24 @@ class LoginView(BaseAppView): # matching emails msg = _('If such email exists, a password reset link was sent to it.') + def default_response(): + log.debug('faking response on invalid password reset') + # make this take 2s, to prevent brute forcing. + time.sleep(2) + h.flash(msg, category='success') + return HTTPFound(self.request.route_path('reset_password')) + if self.request.POST: if h.HasPermissionAny('hg.password_reset.disabled')(): _email = self.request.POST.get('email', '') log.error('Failed attempt to reset password for `%s`.', _email) - h.flash(_('Password reset has been disabled.'), - category='error') + h.flash(_('Password reset has been disabled.'), category='error') return HTTPFound(self.request.route_path('reset_password')) password_reset_form = PasswordResetForm(self.request.translate)() + description = u'Generated token for password reset from {}'.format( + datetime.datetime.now().isoformat()) + try: form_result = password_reset_form.to_python( self.request.POST) @@ -379,10 +405,14 @@ class LoginView(BaseAppView): # Generate reset URL and send mail. user = User.get_by_email(user_email) + # only allow rhodecode based users to reset their password + # external auth shouldn't allow password reset + if user and user.extern_type != auth_rhodecode.RhodeCodeAuthPlugin.uid: + log.warning('User %s with external type `%s` tried a password reset. ' + 'This try was rejected', user, user.extern_type) + return default_response() + # generate password reset token that expires in 10 minutes - description = u'Generated token for password reset from {}'.format( - datetime.datetime.now().isoformat()) - reset_token = UserModel().add_auth_token( user=user, lifetime_minutes=10, role=UserModel.auth_token_role.ROLE_PASSWORD_RESET, @@ -395,15 +425,14 @@ class LoginView(BaseAppView): _query={'key': reset_token.api_key}) UserModel().reset_password_link( form_result, password_reset_url) - # Display success message and redirect. - h.flash(msg, category='success') action_data = {'email': user_email, 'user_agent': self.request.user_agent} audit_logger.store_web( 'user.password.reset_request', action_data=action_data, user=self._rhodecode_user, commit=True) - return HTTPFound(self.request.route_path('reset_password')) + + return default_response() except formencode.Invalid as errors: template_context.update({ @@ -418,11 +447,7 @@ class LoginView(BaseAppView): # case of failed captcha return self._get_template_context(c, **template_context) - log.debug('faking response on invalid password reset') - # make this take 2s, to prevent brute forcing. - time.sleep(2) - h.flash(msg, category='success') - return HTTPFound(self.request.route_path('reset_password')) + return default_response() return self._get_template_context(c, **template_context) diff --git a/rhodecode/apps/repository/utils.py b/rhodecode/apps/repository/utils.py --- a/rhodecode/apps/repository/utils.py +++ b/rhodecode/apps/repository/utils.py @@ -51,12 +51,12 @@ def get_default_reviewers_data( """ Return json for default reviewers of a repository """ reasons = ['Default reviewer', 'Repository owner'] - default = reviewer_as_json( - user=current_user, reasons=reasons, mandatory=False) + json_reviewers = [reviewer_as_json( + user=target_repo.user, reasons=reasons, mandatory=False, rules=None)] return { 'api_ver': 'v1', # define version for later possible schema upgrade - 'reviewers': [default], + 'reviewers': json_reviewers, 'rules': {}, 'rules_data': {}, } diff --git a/rhodecode/apps/repository/views/repo_changelog.py b/rhodecode/apps/repository/views/repo_changelog.py --- a/rhodecode/apps/repository/views/repo_changelog.py +++ b/rhodecode/apps/repository/views/repo_changelog.py @@ -211,7 +211,7 @@ class RepoChangelogView(RepoAppView): base_commit = self.rhodecode_vcs_repo.get_commit(commit_id) try: - collection = base_commit.get_file_history( + collection = base_commit.get_path_history( f_path, limit=hist_limit, pre_load=pre_load) if collection and partial_xhr: # for ajax call we remove first one since we're looking @@ -221,7 +221,7 @@ class RepoChangelogView(RepoAppView): # this node is not present at tip! try: commit = self._get_commit_or_redirect(commit_id) - collection = commit.get_file_history(f_path) + collection = commit.get_path_history(f_path) except RepositoryError as e: h.flash(safe_str(e), category='warning') redirect_url = h.route_path( @@ -315,7 +315,7 @@ class RepoChangelogView(RepoAppView): raise HTTPFound( h.route_path('repo_changelog', repo_name=self.db_repo_name)) - collection = base_commit.get_file_history( + collection = base_commit.get_path_history( f_path, limit=hist_limit, pre_load=pre_load) collection = list(reversed(collection)) else: diff --git a/rhodecode/apps/repository/views/repo_files.py b/rhodecode/apps/repository/views/repo_files.py --- a/rhodecode/apps/repository/views/repo_files.py +++ b/rhodecode/apps/repository/views/repo_files.py @@ -663,7 +663,7 @@ class RepoFilesView(RepoAppView): pass if is_file: - history = commit.get_file_history(f_path) + history = commit.get_path_history(f_path) prev_commit_id = history[1].raw_id \ if len(history) > 1 else prev_commit_id prev_url = h.route_path( @@ -897,10 +897,10 @@ class RepoFilesView(RepoAppView): if commits is None: pre_load = ["author", "branch"] try: - commits = tip.get_file_history(f_path, pre_load=pre_load) + commits = tip.get_path_history(f_path, pre_load=pre_load) except (NodeDoesNotExistError, CommitError): # this node is not present at tip! - commits = commit_obj.get_file_history(f_path, pre_load=pre_load) + commits = commit_obj.get_path_history(f_path, pre_load=pre_load) history = [] commits_group = ([], _("Changesets")) diff --git a/rhodecode/apps/repository/views/repo_review_rules.py b/rhodecode/apps/repository/views/repo_review_rules.py --- a/rhodecode/apps/repository/views/repo_review_rules.py +++ b/rhodecode/apps/repository/views/repo_review_rules.py @@ -25,6 +25,7 @@ from pyramid.view import view_config from rhodecode.apps._base import RepoAppView from rhodecode.apps.repository.utils import get_default_reviewers_data from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator +from rhodecode.model.db import Repository log = logging.getLogger(__name__) @@ -53,8 +54,8 @@ class RepoReviewRulesView(RepoAppView): renderer='json_ext') def repo_default_reviewers_data(self): self.load_default_context() + target_repo_name = self.request.GET.get('target_repo', self.db_repo.repo_name) + target_repo = Repository.get_by_repo_name(target_repo_name) review_data = get_default_reviewers_data( - self.db_repo.user, None, None, None, None) + self.db_repo.user, None, None, target_repo, None) return review_data - - diff --git a/rhodecode/apps/svn_support/utils.py b/rhodecode/apps/svn_support/utils.py --- a/rhodecode/apps/svn_support/utils.py +++ b/rhodecode/apps/svn_support/utils.py @@ -35,15 +35,8 @@ from .events import ModDavSvnConfigChang log = logging.getLogger(__name__) -def generate_mod_dav_svn_config(registry): - """ - Generate the configuration file for use with subversion's mod_dav_svn - module. The configuration has to contain a block for each - available repository group because the mod_dav_svn module does not support - repositories organized in sub folders. - """ - settings = registry.settings - use_ssl = str2bool(registry.settings['force_https']) +def write_mod_dav_svn_config(settings): + use_ssl = str2bool(settings['force_https']) config = _render_mod_dav_svn_config( use_ssl=use_ssl, @@ -54,6 +47,17 @@ def generate_mod_dav_svn_config(registry realm=get_rhodecode_realm(), template=settings[config_keys.template]) _write_mod_dav_svn_config(config, settings[config_keys.config_file_path]) + +def generate_mod_dav_svn_config(registry): + """ + Generate the configuration file for use with subversion's mod_dav_svn + module. The configuration has to contain a block for each + available repository group because the mod_dav_svn module does not support + repositories organized in sub folders. + """ + settings = registry.settings + write_mod_dav_svn_config(settings) + # Trigger an event on mod dav svn configuration change. trigger(ModDavSvnConfigChange(), registry) diff --git a/rhodecode/apps/user_group/__init__.py b/rhodecode/apps/user_group/__init__.py --- a/rhodecode/apps/user_group/__init__.py +++ b/rhodecode/apps/user_group/__init__.py @@ -19,7 +19,7 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ -from rhodecode.apps.admin.navigation import NavigationRegistry +from rhodecode.apps._base.navigation import NavigationRegistry from rhodecode.apps._base import ADMIN_PREFIX from rhodecode.lib.utils2 import str2bool diff --git a/rhodecode/authentication/__init__.py b/rhodecode/authentication/__init__.py --- a/rhodecode/authentication/__init__.py +++ b/rhodecode/authentication/__init__.py @@ -18,11 +18,9 @@ # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ -import os import logging import importlib -from pkg_resources import iter_entry_points from pyramid.authentication import SessionAuthenticationPolicy from rhodecode.authentication.registry import AuthenticationPluginRegistry @@ -31,48 +29,26 @@ from rhodecode.authentication.routes imp from rhodecode.apps._base import ADMIN_PREFIX from rhodecode.model.settings import SettingsModel - log = logging.getLogger(__name__) -# Plugin ID prefixes to distinct between normal and legacy plugins. -plugin_prefix = 'egg:' legacy_plugin_prefix = 'py:' plugin_default_auth_ttl = 30 -# TODO: Currently this is only used to discover the authentication plugins. -# Later on this may be used in a generic way to look up and include all kinds -# of supported enterprise plugins. Therefore this has to be moved and -# refactored to a real 'plugin look up' machinery. -# TODO: When refactoring this think about splitting it up into distinct -# discover, load and include phases. -def _discover_plugins(config, entry_point='enterprise.plugins1'): - for ep in iter_entry_points(entry_point): - plugin_id = '{}{}#{}'.format( - plugin_prefix, ep.dist.project_name, ep.name) - log.debug('Plugin discovered: "%s"', plugin_id) - try: - module = ep.load() - plugin = module(plugin_id=plugin_id) - config.include(plugin.includeme) - except Exception as e: - log.exception( - 'Exception while loading authentication plugin ' - '"{}": {}'.format(plugin_id, e.message)) - - def _import_legacy_plugin(plugin_id): module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1] module = importlib.import_module(module_name) return module.plugin_factory(plugin_id=plugin_id) -def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix): +def discover_legacy_plugins(config, prefix=legacy_plugin_prefix): """ Function that imports the legacy plugins stored in the 'auth_plugins' setting in database which are using the specified prefix. Normally 'py:' is used for the legacy plugins. """ + log.debug('authentication: running legacy plugin discovery for prefix %s', + legacy_plugin_prefix) try: auth_plugins = SettingsModel().get_setting_by_name('auth_plugins') enabled_plugins = auth_plugins.app_settings_value @@ -121,12 +97,3 @@ def includeme(config): request_method='POST', route_name='auth_home', context=AuthnRootResource) - - for key in ['RC_CMD_SETUP_RC', 'RC_CMD_UPGRADE_DB', 'RC_CMD_SSH_WRAPPER']: - if os.environ.get(key): - # skip this heavy step below on certain CLI commands - return - - # Auto discover authentication plugins and include their configuration. - _discover_plugins(config) - _discover_legacy_plugins(config) diff --git a/rhodecode/authentication/base.py b/rhodecode/authentication/base.py --- a/rhodecode/authentication/base.py +++ b/rhodecode/authentication/base.py @@ -38,7 +38,8 @@ from rhodecode.authentication.schema imp from rhodecode.lib import rc_cache from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt from rhodecode.lib.utils2 import safe_int, safe_str -from rhodecode.lib.exceptions import LdapConnectionError +from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \ + LdapPasswordError from rhodecode.model.db import User from rhodecode.model.meta import Session from rhodecode.model.settings import SettingsModel @@ -52,6 +53,8 @@ log = logging.getLogger(__name__) VCS_TYPE = 'vcs' HTTP_TYPE = 'http' +external_auth_session_key = 'rhodecode.external_auth' + class hybrid_property(object): """ @@ -93,6 +96,9 @@ class LazyFormencode(object): class RhodeCodeAuthPluginBase(object): + # UID is used to register plugin to the registry + uid = None + # cache the authentication request for N amount of seconds. Some kind # of authentication methods are very heavy and it's very efficient to cache # the result of a call. If it's set to None (default) cache is off @@ -113,7 +119,7 @@ class RhodeCodeAuthPluginBase(object): "active": 'True|False defines active state of user internally for RhodeCode', "active_from_extern": - "True|False\None, active state from the external auth, " + "True|False|None, active state from the external auth, " "None means use definition from RhodeCode extern_type active value" } @@ -171,6 +177,20 @@ class RhodeCodeAuthPluginBase(object): db_type = '{}.encrypted'.format(db_type) return db_type + @classmethod + def docs(cls): + """ + Defines documentation url which helps with plugin setup + """ + return '' + + @classmethod + def icon(cls): + """ + Defines ICON in SVG format for authentication method + """ + return '' + def is_enabled(self): """ Returns true if this plugin is enabled. An enabled plugin can be @@ -563,7 +583,8 @@ class RhodeCodeExternalAuthPlugin(RhodeC class AuthLdapBase(object): @classmethod - def _build_servers(cls, ldap_server_type, ldap_server, port): + def _build_servers(cls, ldap_server_type, ldap_server, port, use_resolver=True): + def host_resolver(host, port, full_resolve=True): """ Main work for this function is to prevent ldap connection issues, @@ -602,7 +623,7 @@ class AuthLdapBase(object): return ', '.join( ["{}://{}".format( ldap_server_type, - host_resolver(host, port, full_resolve=full_resolve)) + host_resolver(host, port, full_resolve=use_resolver and full_resolve)) for host in ldap_server]) @classmethod @@ -616,6 +637,19 @@ class AuthLdapBase(object): uid = chop_at(username, "@%s" % server_addr) return uid + @classmethod + def validate_username(cls, username): + if "," in username: + raise LdapUsernameError( + "invalid character `,` in username: `{}`".format(username)) + + @classmethod + def validate_password(cls, username, password): + if not password: + msg = "Authenticating user %s with blank password not allowed" + log.warning(msg, username) + raise LdapPasswordError(msg) + def loadplugin(plugin_id): """ diff --git a/rhodecode/authentication/plugins/auth_crowd.py b/rhodecode/authentication/plugins/auth_crowd.py --- a/rhodecode/authentication/plugins/auth_crowd.py +++ b/rhodecode/authentication/plugins/auth_crowd.py @@ -40,7 +40,7 @@ from rhodecode.model.db import User log = logging.getLogger(__name__) -def plugin_factory(plugin_id, *args, **kwds): +def plugin_factory(plugin_id, *args, **kwargs): """ Factory function that is called during plugin discovery. It returns the plugin instance. @@ -189,6 +189,7 @@ class CrowdServer(object): class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin): + uid = 'crowd' _settings_unsafe_keys = ['app_password'] def includeme(self, config): @@ -215,9 +216,13 @@ class RhodeCodeAuthPlugin(RhodeCodeExter def get_display_name(self): return _('CROWD') + @classmethod + def docs(cls): + return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-crowd.html" + @hybrid_property def name(self): - return "crowd" + return u"crowd" def use_fake_password(self): return True @@ -283,3 +288,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExter log.debug("Final crowd user object: \n%s", formatted_json(user_attrs)) log.info('user `%s` authenticated correctly', user_attrs['username']) return user_attrs + + +def includeme(config): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_factory(plugin_id).includeme(config) diff --git a/rhodecode/authentication/plugins/auth_headers.py b/rhodecode/authentication/plugins/auth_headers.py --- a/rhodecode/authentication/plugins/auth_headers.py +++ b/rhodecode/authentication/plugins/auth_headers.py @@ -34,7 +34,7 @@ from rhodecode.model.db import User log = logging.getLogger(__name__) -def plugin_factory(plugin_id, *args, **kwds): +def plugin_factory(plugin_id, *args, **kwargs): """ Factory function that is called during plugin discovery. It returns the plugin instance. @@ -75,7 +75,7 @@ class HeadersSettingsSchema(AuthnPluginS class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin): - + uid = 'headers' def includeme(self, config): config.add_authn_plugin(self) config.add_authn_resource(self.get_id(), HeadersAuthnResource(self)) @@ -102,7 +102,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExter @hybrid_property def name(self): - return 'headers' + return u"headers" @property def is_headers_auth(self): @@ -223,3 +223,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExter log.info('user `%s` authenticated correctly', user_attrs['username']) return user_attrs + + +def includeme(config): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_factory(plugin_id).includeme(config) diff --git a/rhodecode/authentication/plugins/auth_jasig_cas.py b/rhodecode/authentication/plugins/auth_jasig_cas.py --- a/rhodecode/authentication/plugins/auth_jasig_cas.py +++ b/rhodecode/authentication/plugins/auth_jasig_cas.py @@ -42,7 +42,7 @@ from rhodecode.model.db import User log = logging.getLogger(__name__) -def plugin_factory(plugin_id, *args, **kwds): +def plugin_factory(plugin_id, *args, **kwargs): """ Factory function that is called during plugin discovery. It returns the plugin instance. @@ -66,6 +66,7 @@ class JasigCasSettingsSchema(AuthnPlugin class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin): + uid = 'jasig_cas' def includeme(self, config): config.add_authn_plugin(self) @@ -93,7 +94,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExter @hybrid_property def name(self): - return "jasig-cas" + return u"jasig-cas" @property def is_headers_auth(self): @@ -165,3 +166,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExter log.info('user `%s` authenticated correctly', user_attrs['username']) return user_attrs + + +def includeme(config): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_factory(plugin_id).includeme(config) diff --git a/rhodecode/authentication/plugins/auth_ldap.py b/rhodecode/authentication/plugins/auth_ldap.py --- a/rhodecode/authentication/plugins/auth_ldap.py +++ b/rhodecode/authentication/plugins/auth_ldap.py @@ -53,7 +53,7 @@ class LdapError(Exception): pass -def plugin_factory(plugin_id, *args, **kwds): +def plugin_factory(plugin_id, *args, **kwargs): """ Factory function that is called during plugin discovery. It returns the plugin instance. @@ -66,6 +66,171 @@ class LdapAuthnResource(AuthnPluginResou pass +class AuthLdap(AuthLdapBase): + default_tls_cert_dir = '/etc/openldap/cacerts' + + def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', + tls_kind='PLAIN', tls_reqcert='DEMAND', tls_cert_file=None, + tls_cert_dir=None, ldap_version=3, + search_scope='SUBTREE', attr_login='uid', + ldap_filter='', timeout=None): + if ldap == Missing: + raise LdapImportError("Missing or incompatible ldap library") + + self.debug = False + self.timeout = timeout or 60 * 5 + self.ldap_version = ldap_version + self.ldap_server_type = 'ldap' + + self.TLS_KIND = tls_kind + + if self.TLS_KIND == 'LDAPS': + port = port or 689 + self.ldap_server_type += 's' + + OPT_X_TLS_DEMAND = 2 + self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, OPT_X_TLS_DEMAND) + self.TLS_CERT_FILE = tls_cert_file or '' + self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir + + # split server into list + self.SERVER_ADDRESSES = self._get_server_list(server) + self.LDAP_SERVER_PORT = port + + # USE FOR READ ONLY BIND TO LDAP SERVER + self.attr_login = attr_login + + self.LDAP_BIND_DN = safe_str(bind_dn) + self.LDAP_BIND_PASS = safe_str(bind_pass) + + self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) + self.BASE_DN = safe_str(base_dn) + self.LDAP_FILTER = safe_str(ldap_filter) + + def _get_ldap_conn(self): + + if self.debug: + ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) + + if self.TLS_CERT_FILE and hasattr(ldap, 'OPT_X_TLS_CACERTFILE'): + ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE) + + elif hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): + ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR) + + if self.TLS_KIND != 'PLAIN': + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT) + + ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) + ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) + + # init connection now + ldap_servers = self._build_servers( + self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT) + log.debug('initializing LDAP connection to:%s', ldap_servers) + ldap_conn = ldap.initialize(ldap_servers) + ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout) + ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout) + ldap_conn.timeout = self.timeout + + if self.ldap_version == 2: + ldap_conn.protocol = ldap.VERSION2 + else: + ldap_conn.protocol = ldap.VERSION3 + + if self.TLS_KIND == 'START_TLS': + ldap_conn.start_tls_s() + + if self.LDAP_BIND_DN and self.LDAP_BIND_PASS: + log.debug('Trying simple_bind with password and given login DN: %r', + self.LDAP_BIND_DN) + ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) + + return ldap_conn + + def fetch_attrs_from_simple_bind(self, server, dn, username, password): + try: + log.debug('Trying simple bind with %r', dn) + server.simple_bind_s(dn, safe_str(password)) + user = server.search_ext_s( + dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0] + _, attrs = user + return attrs + + except ldap.INVALID_CREDENTIALS: + log.debug( + "LDAP rejected password for user '%s': %s, org_exc:", + username, dn, exc_info=True) + + def authenticate_ldap(self, username, password): + """ + Authenticate a user via LDAP and return his/her LDAP properties. + + Raises AuthenticationError if the credentials are rejected, or + EnvironmentError if the LDAP server can't be reached. + + :param username: username + :param password: password + """ + + uid = self.get_uid(username, self.SERVER_ADDRESSES) + user_attrs = {} + dn = '' + + self.validate_password(username, password) + self.validate_username(username) + + ldap_conn = None + try: + ldap_conn = self._get_ldap_conn() + filter_ = '(&%s(%s=%s))' % ( + self.LDAP_FILTER, self.attr_login, username) + log.debug("Authenticating %r filter %s", self.BASE_DN, filter_) + + lobjects = ldap_conn.search_ext_s( + self.BASE_DN, self.SEARCH_SCOPE, filter_) + + if not lobjects: + log.debug("No matching LDAP objects for authentication " + "of UID:'%s' username:(%s)", uid, username) + raise ldap.NO_SUCH_OBJECT() + + log.debug('Found matching ldap object, trying to authenticate') + for (dn, _attrs) in lobjects: + if dn is None: + continue + + user_attrs = self.fetch_attrs_from_simple_bind( + ldap_conn, dn, username, password) + if user_attrs: + break + else: + raise LdapPasswordError( + 'Failed to authenticate user `{}`' + 'with given password'.format(username)) + + except ldap.NO_SUCH_OBJECT: + log.debug("LDAP says no such user '%s' (%s), org_exc:", + uid, username, exc_info=True) + raise LdapUsernameError('Unable to find user') + except ldap.SERVER_DOWN: + org_exc = traceback.format_exc() + raise LdapConnectionError( + "LDAP can't access authentication " + "server, org_exc:%s" % org_exc) + finally: + if ldap_conn: + log.debug('ldap: connection release') + try: + ldap_conn.unbind_s() + except Exception: + # for any reason this can raise exception we must catch it + # to not crush the server + pass + + return dn, user_attrs + + class LdapSettingsSchema(AuthnPluginSettingsSchemaBase): tls_kind_choices = ['PLAIN', 'LDAPS', 'START_TLS'] tls_reqcert_choices = ['NEVER', 'ALLOW', 'TRY', 'DEMAND', 'HARD'] @@ -84,7 +249,7 @@ class LdapSettingsSchema(AuthnPluginSett colander.Int(), default=389, description=_('Custom port that the LDAP server is listening on. ' - 'Default value is: 389'), + 'Default value is: 389, use 689 for LDAPS(SSL)'), preparer=strip_whitespace, title=_('Port'), validator=colander.Range(min=0, max=65536), @@ -133,6 +298,22 @@ class LdapSettingsSchema(AuthnPluginSett title=_('Certificate Checks'), validator=colander.OneOf(tls_reqcert_choices), widget='select') + tls_cert_file = colander.SchemaNode( + colander.String(), + default='', + description=_('This specifies the PEM-format file path containing ' + 'certificates for use in TLS connection.\n' + 'If not specified `TLS Cert dir` will be used'), + title=_('TLS Cert file'), + missing='', + widget='string') + tls_cert_dir = colander.SchemaNode( + colander.String(), + default=AuthLdap.default_tls_cert_dir, + description=_('This specifies the path of a directory that contains individual ' + 'CA certificates in separate files.'), + title=_('TLS Cert dir'), + widget='string') base_dn = colander.SchemaNode( colander.String(), default='', @@ -169,6 +350,16 @@ class LdapSettingsSchema(AuthnPluginSett title=_('Login Attribute'), missing_msg=_('The LDAP Login attribute of the CN must be specified'), widget='string') + attr_email = colander.SchemaNode( + colander.String(), + default='', + description=_('LDAP Attribute to map to email address (e.g., mail).\n' + 'Emails are a crucial part of RhodeCode. \n' + 'If possible add a valid email attribute to ldap users.'), + missing='', + preparer=strip_whitespace, + title=_('Email Attribute'), + widget='string') attr_firstname = colander.SchemaNode( colander.String(), default='', @@ -185,182 +376,10 @@ class LdapSettingsSchema(AuthnPluginSett preparer=strip_whitespace, title=_('Last Name Attribute'), widget='string') - attr_email = colander.SchemaNode( - colander.String(), - default='', - description=_('LDAP Attribute to map to email address (e.g., mail).\n' - 'Emails are a crucial part of RhodeCode. \n' - 'If possible add a valid email attribute to ldap users.'), - missing='', - preparer=strip_whitespace, - title=_('Email Attribute'), - widget='string') - - -class AuthLdap(AuthLdapBase): - - def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='', - tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3, - search_scope='SUBTREE', attr_login='uid', - ldap_filter='', timeout=None): - if ldap == Missing: - raise LdapImportError("Missing or incompatible ldap library") - - self.debug = False - self.timeout = timeout or 60 * 5 - self.ldap_version = ldap_version - self.ldap_server_type = 'ldap' - - self.TLS_KIND = tls_kind - - if self.TLS_KIND == 'LDAPS': - port = port or 689 - self.ldap_server_type += 's' - - OPT_X_TLS_DEMAND = 2 - self.TLS_REQCERT = getattr(ldap, 'OPT_X_TLS_%s' % tls_reqcert, - OPT_X_TLS_DEMAND) - self.LDAP_SERVER = server - # split server into list - self.SERVER_ADDRESSES = self._get_server_list(server) - self.LDAP_SERVER_PORT = port - - # USE FOR READ ONLY BIND TO LDAP SERVER - self.attr_login = attr_login - - self.LDAP_BIND_DN = safe_str(bind_dn) - self.LDAP_BIND_PASS = safe_str(bind_pass) - - self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope) - self.BASE_DN = safe_str(base_dn) - self.LDAP_FILTER = safe_str(ldap_filter) - - def _get_ldap_conn(self): - - if self.debug: - ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255) - - if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'): - ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts') - if self.TLS_KIND != 'PLAIN': - ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT) - - ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF) - ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON) - - # init connection now - ldap_servers = self._build_servers( - self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT) - log.debug('initializing LDAP connection to:%s', ldap_servers) - ldap_conn = ldap.initialize(ldap_servers) - ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout) - ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout) - ldap_conn.timeout = self.timeout - - if self.ldap_version == 2: - ldap_conn.protocol = ldap.VERSION2 - else: - ldap_conn.protocol = ldap.VERSION3 - - if self.TLS_KIND == 'START_TLS': - ldap_conn.start_tls_s() - - if self.LDAP_BIND_DN and self.LDAP_BIND_PASS: - log.debug('Trying simple_bind with password and given login DN: %s', - self.LDAP_BIND_DN) - ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS) - - return ldap_conn - - def fetch_attrs_from_simple_bind(self, server, dn, username, password): - try: - log.debug('Trying simple bind with %s', dn) - server.simple_bind_s(dn, safe_str(password)) - user = server.search_ext_s( - dn, ldap.SCOPE_BASE, '(objectClass=*)', )[0] - _, attrs = user - return attrs - - except ldap.INVALID_CREDENTIALS: - log.debug( - "LDAP rejected password for user '%s': %s, org_exc:", - username, dn, exc_info=True) - - def authenticate_ldap(self, username, password): - """ - Authenticate a user via LDAP and return his/her LDAP properties. - - Raises AuthenticationError if the credentials are rejected, or - EnvironmentError if the LDAP server can't be reached. - - :param username: username - :param password: password - """ - - uid = self.get_uid(username, self.SERVER_ADDRESSES) - user_attrs = {} - dn = '' - - if not password: - msg = "Authenticating user %s with blank password not allowed" - log.warning(msg, username) - raise LdapPasswordError(msg) - if "," in username: - raise LdapUsernameError( - "invalid character `,` in username: `{}`".format(username)) - ldap_conn = None - try: - ldap_conn = self._get_ldap_conn() - filter_ = '(&%s(%s=%s))' % ( - self.LDAP_FILTER, self.attr_login, username) - log.debug( - "Authenticating %r filter %s", self.BASE_DN, filter_) - lobjects = ldap_conn.search_ext_s( - self.BASE_DN, self.SEARCH_SCOPE, filter_) - - if not lobjects: - log.debug("No matching LDAP objects for authentication " - "of UID:'%s' username:(%s)", uid, username) - raise ldap.NO_SUCH_OBJECT() - - log.debug('Found matching ldap object, trying to authenticate') - for (dn, _attrs) in lobjects: - if dn is None: - continue - - user_attrs = self.fetch_attrs_from_simple_bind( - ldap_conn, dn, username, password) - if user_attrs: - break - - else: - raise LdapPasswordError( - 'Failed to authenticate user `{}`' - 'with given password'.format(username)) - - except ldap.NO_SUCH_OBJECT: - log.debug("LDAP says no such user '%s' (%s), org_exc:", - uid, username, exc_info=True) - raise LdapUsernameError('Unable to find user') - except ldap.SERVER_DOWN: - org_exc = traceback.format_exc() - raise LdapConnectionError( - "LDAP can't access authentication " - "server, org_exc:%s" % org_exc) - finally: - if ldap_conn: - log.debug('ldap: connection release') - try: - ldap_conn.unbind_s() - except Exception: - # for any reason this can raise exception we must catch it - # to not crush the server - pass - - return dn, user_attrs class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin): + uid = 'ldap' # used to define dynamic binding in the DYNAMIC_BIND_VAR = '$login' _settings_unsafe_keys = ['dn_pass'] @@ -389,9 +408,13 @@ class RhodeCodeAuthPlugin(RhodeCodeExter def get_display_name(self): return _('LDAP') + @classmethod + def docs(cls): + return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-ldap.html" + @hybrid_property def name(self): - return "ldap" + return u"ldap" def use_fake_password(self): return True @@ -448,6 +471,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExter 'bind_pass': settings.get('dn_pass'), 'tls_kind': settings.get('tls_kind'), 'tls_reqcert': settings.get('tls_reqcert'), + 'tls_cert_file': settings.get('tls_cert_file'), + 'tls_cert_dir': settings.get('tls_cert_dir'), 'search_scope': settings.get('search_scope'), 'attr_login': settings.get('attr_login'), 'ldap_version': 3, @@ -477,12 +502,11 @@ class RhodeCodeAuthPlugin(RhodeCodeExter extern_type = getattr(userobj, 'extern_type', '') groups = [] + user_attrs = { 'username': username, - 'firstname': safe_unicode( - get_ldap_attr('attr_firstname') or firstname), - 'lastname': safe_unicode( - get_ldap_attr('attr_lastname') or lastname), + 'firstname': safe_unicode(get_ldap_attr('attr_firstname') or firstname), + 'lastname': safe_unicode(get_ldap_attr('attr_lastname') or lastname), 'groups': groups, 'user_group_sync': False, 'email': get_ldap_attr('attr_email') or email, @@ -492,6 +516,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExter 'extern_name': user_dn, 'extern_type': extern_type, } + log.debug('ldap user: %s', user_attrs) log.info('user `%s` authenticated correctly', user_attrs['username']) @@ -504,3 +529,7 @@ class RhodeCodeAuthPlugin(RhodeCodeExter log.exception("Other exception") return None + +def includeme(config): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_factory(plugin_id).includeme(config) diff --git a/rhodecode/authentication/plugins/auth_pam.py b/rhodecode/authentication/plugins/auth_pam.py --- a/rhodecode/authentication/plugins/auth_pam.py +++ b/rhodecode/authentication/plugins/auth_pam.py @@ -40,7 +40,7 @@ from rhodecode.lib.colander_utils import log = logging.getLogger(__name__) -def plugin_factory(plugin_id, *args, **kwds): +def plugin_factory(plugin_id, *args, **kwargs): """ Factory function that is called during plugin discovery. It returns the plugin instance. @@ -72,6 +72,7 @@ class PamSettingsSchema(AuthnPluginSetti class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin): + uid = 'pam' # PAM authentication can be slow. Repository operations involve a lot of # auth calls. Little caching helps speedup push/pull operations significantly AUTH_CACHE_TTL = 4 @@ -97,9 +98,13 @@ class RhodeCodeAuthPlugin(RhodeCodeExter def get_display_name(self): return _('PAM') + @classmethod + def docs(cls): + return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-pam.html" + @hybrid_property def name(self): - return "pam" + return u"pam" def get_settings_schema(self): return PamSettingsSchema() @@ -159,3 +164,8 @@ class RhodeCodeAuthPlugin(RhodeCodeExter log.debug("pamuser: %s", user_attrs) log.info('user `%s` authenticated correctly', user_attrs['username']) return user_attrs + + +def includeme(config): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_factory(plugin_id).includeme(config) diff --git a/rhodecode/authentication/plugins/auth_rhodecode.py b/rhodecode/authentication/plugins/auth_rhodecode.py --- a/rhodecode/authentication/plugins/auth_rhodecode.py +++ b/rhodecode/authentication/plugins/auth_rhodecode.py @@ -34,7 +34,7 @@ from rhodecode.model.db import User log = logging.getLogger(__name__) -def plugin_factory(plugin_id, *args, **kwds): +def plugin_factory(plugin_id, *args, **kwargs): plugin = RhodeCodeAuthPlugin(plugin_id) return plugin @@ -44,6 +44,7 @@ class RhodecodeAuthnResource(AuthnPlugin class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase): + uid = 'rhodecode' def includeme(self, config): config.add_authn_plugin(self) @@ -64,11 +65,15 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP context=RhodecodeAuthnResource) def get_display_name(self): - return _('Rhodecode') + return _('RhodeCode Internal') + + @classmethod + def docs(cls): + return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth.html" @hybrid_property def name(self): - return "rhodecode" + return u"rhodecode" def user_activation_state(self): def_user_perms = User.get_default_user().AuthUser().permissions['global'] @@ -141,3 +146,8 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP 'user `%s` failed to authenticate via %s, reason: account not ' 'active.', username, self.name) return None + + +def includeme(config): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_factory(plugin_id).includeme(config) diff --git a/rhodecode/authentication/plugins/auth_token.py b/rhodecode/authentication/plugins/auth_token.py --- a/rhodecode/authentication/plugins/auth_token.py +++ b/rhodecode/authentication/plugins/auth_token.py @@ -34,7 +34,7 @@ from rhodecode.model.db import User, Use log = logging.getLogger(__name__) -def plugin_factory(plugin_id, *args, **kwds): +def plugin_factory(plugin_id, *args, **kwargs): plugin = RhodeCodeAuthPlugin(plugin_id) return plugin @@ -47,6 +47,7 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP """ Enables usage of authentication tokens for vcs operations. """ + uid = 'token' def includeme(self, config): config.add_authn_plugin(self) @@ -67,11 +68,15 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP context=RhodecodeAuthnResource) def get_display_name(self): - return _('Rhodecode Token Auth') + return _('Rhodecode Token') + + @classmethod + def docs(cls): + return "https://docs.rhodecode.com/RhodeCode-Enterprise/auth/auth-token.html" @hybrid_property def name(self): - return "authtoken" + return u"authtoken" def user_activation_state(self): def_user_perms = User.get_default_user().AuthUser().permissions['global'] @@ -145,3 +150,8 @@ class RhodeCodeAuthPlugin(RhodeCodeAuthP 'user `%s` failed to authenticate via %s, reason: account not ' 'active.', username, self.name) return None + + +def includeme(config): + plugin_id = 'egg:rhodecode-enterprise-ce#{}'.format(RhodeCodeAuthPlugin.uid) + plugin_factory(plugin_id).includeme(config) diff --git a/rhodecode/authentication/routes.py b/rhodecode/authentication/routes.py --- a/rhodecode/authentication/routes.py +++ b/rhodecode/authentication/routes.py @@ -19,6 +19,7 @@ # and proprietary license terms, please see https://rhodecode.com/licenses/ import logging +import collections from pyramid.exceptions import ConfigurationError @@ -55,9 +56,9 @@ class AuthnRootResource(AuthnResourceBas """ def __init__(self): - self._store = {} + self._store = collections.OrderedDict() self._resource_name_map = {} - self.display_name = _('Global') + self.display_name = _('Authentication Plugins') def __getitem__(self, key): """ @@ -87,23 +88,27 @@ class AuthnRootResource(AuthnResourceBas # TODO: Store this info in the resource element. return self._resource_name_map[resource_name] - def get_sorted_list(self): + def get_sorted_list(self, sort_key=None): """ Returns a sorted list of sub resources for displaying purposes. """ - def sort_key(resource): + def default_sort_key(resource): return str.lower(safe_str(resource.display_name)) active = [item for item in self] - return sorted(active, key=sort_key) + return sorted(active, key=sort_key or default_sort_key) - def get_nav_list(self): + def get_nav_list(self, sort=True): """ Returns a sorted list of resources for displaying the navigation. """ - list = self.get_sorted_list() - list.insert(0, self) - return list + if sort: + nav_list = self.get_sorted_list() + else: + nav_list = [item for item in self] + + nav_list.insert(0, self) + return nav_list def add_authn_resource(self, config, plugin_id, resource): """ diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py --- a/rhodecode/config/middleware.py +++ b/rhodecode/config/middleware.py @@ -23,6 +23,7 @@ import sys import logging import collections import tempfile +import time from paste.gzipper import make_gzip_middleware import pyramid.events @@ -63,6 +64,14 @@ def is_http_error(response): return response.status_code > 499 +def should_load_all(): + """ + Returns if all application components should be loaded. In some cases it's + desired to skip apps loading for faster shell script execution + """ + return True + + def make_pyramid_app(global_config, **settings): """ Constructs the WSGI application based on Pyramid. @@ -78,8 +87,13 @@ def make_pyramid_app(global_config, **se # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It # will be replaced by the value of the environment variable "NAME" in this case. - environ = { - 'ENV_{}'.format(key): value for key, value in os.environ.items()} + start_time = time.time() + + debug = asbool(global_config.get('debug')) + if debug: + enable_debug() + + environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()} global_config = _substitute_values(global_config, environ) settings = _substitute_values(settings, environ) @@ -105,8 +119,10 @@ def make_pyramid_app(global_config, **se config.configure_celery(global_config['__file__']) # creating the app uses a connection - return it after we are done meta.Session.remove() + total_time = time.time() - start_time + log.info('Pyramid app `%s` created and configured in %.2fs', + pyramid_app.func_name, total_time) - log.info('Pyramid app %s created and configured.', pyramid_app) return pyramid_app @@ -215,6 +231,7 @@ def includeme_first(config): def includeme(config): + log.debug('Initializing main includeme from %s', os.path.basename(__file__)) settings = config.registry.settings config.set_request_factory(Request) @@ -229,41 +246,58 @@ def includeme(config): if asbool(settings.get('appenlight', 'false')): config.include('appenlight_client.ext.pyramid_tween') + load_all = should_load_all() + # Includes which are required. The application would fail without them. config.include('pyramid_mako') config.include('pyramid_beaker') config.include('rhodecode.lib.rc_cache') + config.include('rhodecode.apps._base.navigation') + config.include('rhodecode.apps._base.subscribers') + config.include('rhodecode.tweens') + + config.include('rhodecode.integrations') config.include('rhodecode.authentication') - config.include('rhodecode.integrations') + + if load_all: + from rhodecode.authentication import discover_legacy_plugins + # load CE authentication plugins + config.include('rhodecode.authentication.plugins.auth_crowd') + config.include('rhodecode.authentication.plugins.auth_headers') + config.include('rhodecode.authentication.plugins.auth_jasig_cas') + config.include('rhodecode.authentication.plugins.auth_ldap') + config.include('rhodecode.authentication.plugins.auth_pam') + config.include('rhodecode.authentication.plugins.auth_rhodecode') + config.include('rhodecode.authentication.plugins.auth_token') + + # Auto discover authentication plugins and include their configuration. + discover_legacy_plugins(config) # apps config.include('rhodecode.apps._base') - config.include('rhodecode.apps.ops') - config.include('rhodecode.apps.admin') - config.include('rhodecode.apps.channelstream') - config.include('rhodecode.apps.login') - config.include('rhodecode.apps.home') - config.include('rhodecode.apps.journal') - config.include('rhodecode.apps.repository') - config.include('rhodecode.apps.repo_group') - config.include('rhodecode.apps.user_group') - config.include('rhodecode.apps.search') - config.include('rhodecode.apps.user_profile') - config.include('rhodecode.apps.user_group_profile') - config.include('rhodecode.apps.my_account') - config.include('rhodecode.apps.svn_support') - config.include('rhodecode.apps.ssh_support') - config.include('rhodecode.apps.gist') + if load_all: + config.include('rhodecode.apps.ops') + config.include('rhodecode.apps.admin') + config.include('rhodecode.apps.channelstream') + config.include('rhodecode.apps.login') + config.include('rhodecode.apps.home') + config.include('rhodecode.apps.journal') + config.include('rhodecode.apps.repository') + config.include('rhodecode.apps.repo_group') + config.include('rhodecode.apps.user_group') + config.include('rhodecode.apps.search') + config.include('rhodecode.apps.user_profile') + config.include('rhodecode.apps.user_group_profile') + config.include('rhodecode.apps.my_account') + config.include('rhodecode.apps.svn_support') + config.include('rhodecode.apps.ssh_support') + config.include('rhodecode.apps.gist') + config.include('rhodecode.apps.debug_style') + config.include('rhodecode.api') - config.include('rhodecode.apps.debug_style') - config.include('rhodecode.tweens') - config.include('rhodecode.api') - - config.add_route( - 'rhodecode_support', 'https://rhodecode.com/help/', static=True) - + config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True) config.add_translation_dirs('rhodecode:i18n/') settings['default_locale_name'] = settings.get('lang', 'en') @@ -403,6 +437,114 @@ def sanitize_settings_and_apply_defaults return settings +def enable_debug(): + """ + Helper to enable debug on running instance + :return: + """ + import tempfile + import textwrap + import logging.config + + ini_template = textwrap.dedent(""" + ##################################### + ### DEBUG LOGGING CONFIGURATION #### + ##################################### + [loggers] + keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper + + [handlers] + keys = console, console_sql + + [formatters] + keys = generic, color_formatter, color_formatter_sql + + ############# + ## LOGGERS ## + ############# + [logger_root] + level = NOTSET + handlers = console + + [logger_sqlalchemy] + level = INFO + handlers = console_sql + qualname = sqlalchemy.engine + propagate = 0 + + [logger_beaker] + level = DEBUG + handlers = + qualname = beaker.container + propagate = 1 + + [logger_rhodecode] + level = DEBUG + handlers = + qualname = rhodecode + propagate = 1 + + [logger_ssh_wrapper] + level = DEBUG + handlers = + qualname = ssh_wrapper + propagate = 1 + + [logger_celery] + level = DEBUG + handlers = + qualname = celery + + + ############## + ## HANDLERS ## + ############## + + [handler_console] + class = StreamHandler + args = (sys.stderr, ) + level = DEBUG + formatter = color_formatter + + [handler_console_sql] + # "level = DEBUG" logs SQL queries and results. + # "level = INFO" logs SQL queries. + # "level = WARN" logs neither. (Recommended for production systems.) + class = StreamHandler + args = (sys.stderr, ) + level = WARN + formatter = color_formatter_sql + + ################ + ## FORMATTERS ## + ################ + + [formatter_generic] + class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter + format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s + datefmt = %Y-%m-%d %H:%M:%S + + [formatter_color_formatter] + class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter + format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s + datefmt = %Y-%m-%d %H:%M:%S + + [formatter_color_formatter_sql] + class = rhodecode.lib.logging_formatter.ColorFormatterSql + format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s + datefmt = %Y-%m-%d %H:%M:%S + """) + + with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini', + delete=False) as f: + log.info('Saved Temporary DEBUG config at %s', f.name) + f.write(ini_template) + + logging.config.fileConfig(f.name) + log.debug('DEBUG MODE ON') + os.remove(f.name) + + def _sanitize_appenlight_settings(settings): _bool_setting(settings, 'appenlight', 'false') @@ -447,7 +589,7 @@ def _sanitize_cache_settings(settings): # ensure we have our dir created if not os.path.isdir(default_cache_dir): - os.makedirs(default_cache_dir, mode=0755) + os.makedirs(default_cache_dir, mode=0o755) # exception store cache _string_setting( @@ -578,5 +720,8 @@ def _substitute_values(mapping, substitu raise ValueError( 'Failed to substitute env variable: {}. ' 'Make sure you have specified this env variable without ENV_ prefix'.format(e)) + except ValueError as e: + log.warning('Failed to substitute ENV variable: %s', e) + result = mapping return result diff --git a/rhodecode/config/rcextensions/examples/custom_email_integration_template.py b/rhodecode/config/rcextensions/examples/custom_email_integration_template.py new file mode 100644 --- /dev/null +++ b/rhodecode/config/rcextensions/examples/custom_email_integration_template.py @@ -0,0 +1,175 @@ +# This code allows override the integrations templates. Put this into the __init__.py +# file of rcextensions + + +# EMAIL +from rhodecode.integrations import email +email.REPO_PUSH_TEMPLATE_HTML = email.Template(''' + + + + + + ${subject} + + + + + + + + + + + + + +
+ + + + + +
+ + ${'RhodeCode'} + +
+ % if data['push']['commits']: + % for commit in data['push']['commits']: + ${commit['short_id']} by ${commit['author']} at ${commit['date']}
+ ${commit['message_html']}
+
+ % endfor + % else: + No commit data + % endif +
+
+ +

+ + +''') + + +# JIRA (EE ONLY) +from rc_integrations import jira_tracker + +jira_tracker.COMMENT_TEMPLATE_PULL_REQUEST = jira_tracker.Template(''' +${action} by ${author} (status: ${status}). \n +pull-request: ${url} +''') + + +jira_tracker.COMMENT_TEMPLATE_COMMIT = jira_tracker.Template(''' +Commit `${short_id}` by ${author} on `${branch}` branch references this issue. \n +${url}\n + +## MODIFICATION add custom COMMIT message to the comment +${commit['message']} +''') + + +jira_tracker.COMMENT_TEMPLATE_COMMIT_WITH_STATUS = jira_tracker.Template(''' +Commit `${short_id}` by ${author} on `${branch}` branch changed this issue. \n +'{url}\n + +## MODIFICATION add custom COMMIT message to the comment +${commit['message']} +''') + + +# REDMINE (EE ONLY) +from rc_integrations import redmine_tracker + +redmine_tracker.COMMENT_TEMPLATE_COMMIT = redmine_tracker.Template(''' +Commit `${short_id}` by ${author} on `${branch}` branch references this issue. \n +commit: ${url}\n + +## MODIFICATION add custom COMMIT message to the comment +message: +``` +${commit['message']} +``` + +''') + +redmine_tracker.COMMENT_TEMPLATE_COMMIT_WITH_STATUS = redmine_tracker.Template(''' +Commit `${short_id}` by ${author} on `${branch}` branch changed this issue. \n +commit: ${url}\n + +## MODIFICATION add custom COMMIT message to the comment +message: +``` +${commit['message']} +``` + +''') + +redmine_tracker.COMMENT_TEMPLATE_PULL_REQUEST = redmine_tracker.Template(''' +${action} by ${author} (status: ${status}). \n' +${url}\n + +## MODIFICATION add custom COMMIT message to the comment +message: +``` +${commit['message']} +``` + +''') diff --git a/rhodecode/config/rcextensions/examples/validate_author.py b/rhodecode/config/rcextensions/examples/validate_author.py deleted file mode 100644 diff --git a/rhodecode/config/rcextensions/examples/validate_commit_message.py b/rhodecode/config/rcextensions/examples/validate_commit_message.py deleted file mode 100644 diff --git a/rhodecode/config/rcextensions/examples/validate_commit_message_author.py b/rhodecode/config/rcextensions/examples/validate_commit_message_author.py new file mode 100644 --- /dev/null +++ b/rhodecode/config/rcextensions/examples/validate_commit_message_author.py @@ -0,0 +1,91 @@ +# Example to validate commit message or author using some sort of rules + + +@has_kwargs({ + 'server_url': 'url of instance that triggered this hook', + 'config': 'path to .ini config used', + 'scm': 'type of version control "git", "hg", "svn"', + 'username': 'username of actor who triggered this event', + 'ip': 'ip address of actor who triggered this hook', + 'action': '', + 'repository': 'repository name', + 'repo_store_path': 'full path to where repositories are stored', + 'commit_ids': 'pre transaction metadata for commit ids', + 'hook_type': '', + 'user_agent': 'Client user agent, e.g git or mercurial CLI version', +}) +@has_kwargs({ + 'server_url': 'url of instance that triggered this hook', + 'config': 'path to .ini config used', + 'scm': 'type of version control "git", "hg", "svn"', + 'username': 'username of actor who triggered this event', + 'ip': 'ip address of actor who triggered this hook', + 'action': '', + 'repository': 'repository name', + 'repo_store_path': 'full path to where repositories are stored', + 'commit_ids': 'pre transaction metadata for commit ids', + 'hook_type': '', + 'user_agent': 'Client user agent, e.g git or mercurial CLI version', +}) +def _pre_push_hook(*args, **kwargs): + """ + Post push hook + To stop version control from storing the transaction and send a message to user + use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed') + + This message will be shown back to client during PUSH operation + + Commit ids might look like that:: + + [{u'hg_env|git_env': ..., + u'multiple_heads': [], + u'name': u'default', + u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69', + u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69', + u'ref': u'', + u'total_commits': 2, + u'type': u'branch'}] + """ + import re + from .helpers import extra_fields, extract_pre_commits + from .utils import str2bool + + # returns list of dicts with key-val fetched from extra fields + repo_extra_fields = extra_fields.run(**kwargs) + + # optionally use 'extra fields' to control the logic per repo + should_validate = str2bool(repo_extra_fields.get('validate_author', True)) + + # optionally store validation regex into extra fields + validation_regex = repo_extra_fields.get('validation_regex', '') + + def validate_commit_message(commit_message, message_regex=None): + """ + This function validates commit_message against some sort of rules. + It should return a valid boolean, and a reason for failure + """ + + if "secret_string" in commit_message: + msg = "!!Push forbidden: secret string found in commit messages" + return False, msg + + if validation_regex: + regexp = re.compile(validation_regex) + if not regexp.match(message): + msg = "!!Push forbidden: commit message does not match regexp" + return False, msg + + return True, '' + + if should_validate: + # returns list of dicts with key-val fetched from extra fields + commit_list = extract_pre_commits.run(**kwargs) + + for commit_data in commit_list: + message = commit_data['message'] + + message_valid, reason = validate_commit_message(message, validation_regex) + if not message_valid: + return HookResponse(1, reason) + + return HookResponse(0, '') diff --git a/rhodecode/config/rcextensions/helpers/extract_pre_commits.py b/rhodecode/config/rcextensions/helpers/extract_pre_commits.py --- a/rhodecode/config/rcextensions/helpers/extract_pre_commits.py +++ b/rhodecode/config/rcextensions/helpers/extract_pre_commits.py @@ -27,6 +27,7 @@ us in hooks:: """ import re import collections +import json def get_hg_commits(repo, refs): @@ -36,6 +37,31 @@ def get_hg_commits(repo, refs): def get_git_commits(repo, refs): commits = [] + + for data in refs: + # we should now extract commit data + old_rev = data['old_rev'] + new_rev = data['new_rev'] + + if '00000000' in old_rev: + # new branch, we don't need to extract nothing + return commits + + git_env = dict(data['git_env']) + cmd = [ + 'log', + '--pretty=format:{"commit_id": "%H", "author": "%aN <%aE>", "date": "%ad", "message": "%f"}', + '{}...{}'.format(old_rev, new_rev) + ] + + stdout, stderr = repo.run_git_command(cmd, extra_env=git_env) + for line in stdout.splitlines(): + try: + data = json.loads(line) + commits.append(data) + except Exception: + print('Failed to load data from GIT line') + return commits @@ -51,13 +77,14 @@ def run(*args, **kwargs): commits = [] - for rev_data in kwargs['commit_ids']: - new_environ = dict((k, v) for k, v in rev_data['hg_env']) - if vcs_type == 'git': + for rev_data in kwargs['commit_ids']: + new_environ = dict((k, v) for k, v in rev_data['git_env']) commits = get_git_commits(vcs_repo, kwargs['commit_ids']) if vcs_type == 'hg': + for rev_data in kwargs['commit_ids']: + new_environ = dict((k, v) for k, v in rev_data['hg_env']) commits = get_hg_commits(vcs_repo, kwargs['commit_ids']) return commits diff --git a/rhodecode/config/rcextensions/utils.py b/rhodecode/config/rcextensions/utils.py --- a/rhodecode/config/rcextensions/utils.py +++ b/rhodecode/config/rcextensions/utils.py @@ -145,3 +145,41 @@ def maybe_log_call(name, args, kwargs): if hasattr(rcextensions, 'calls'): calls = rcextensions.calls calls[name].append((args, kwargs)) + + +def str2bool(_str): + """ + returns True/False value from given string, it tries to translate the + string into boolean + + :param _str: string value to translate into boolean + :rtype: boolean + :returns: boolean from given string + """ + if _str is None: + return False + if _str in (True, False): + return _str + _str = str(_str).strip().lower() + return _str in ('t', 'true', 'y', 'yes', 'on', '1') + + +def aslist(obj, sep=None, strip=True): + """ + Returns given string separated by sep as list + + :param obj: + :param sep: + :param strip: + """ + if isinstance(obj, (basestring,)): + lst = obj.split(sep) + if strip: + lst = [v.strip() for v in lst] + return lst + elif isinstance(obj, (list, tuple)): + return obj + elif obj is None: + return [] + else: + return [obj] \ No newline at end of file diff --git a/rhodecode/config/routing_links.py b/rhodecode/config/routing_links.py --- a/rhodecode/config/routing_links.py +++ b/rhodecode/config/routing_links.py @@ -43,7 +43,7 @@ then you can retrieve the url by simply The redirection must be first implemented in our servers before you can see it working. """ -# flake8: noqa +# pragma: no cover from __future__ import unicode_literals link_config = [ diff --git a/rhodecode/events/__init__.py b/rhodecode/events/__init__.py --- a/rhodecode/events/__init__.py +++ b/rhodecode/events/__init__.py @@ -44,7 +44,7 @@ def trigger(event, registry=None): integrations_event_handler(event) -from rhodecode.events.user import ( # noqa +from rhodecode.events.user import ( # pragma: no cover UserPreCreate, UserPostCreate, UserPreUpdate, @@ -52,7 +52,7 @@ from rhodecode.events.user import ( # n UserPermissionsChange, ) -from rhodecode.events.repo import ( # noqa +from rhodecode.events.repo import ( # pragma: no cover RepoEvent, RepoPreCreateEvent, RepoCreateEvent, RepoPreDeleteEvent, RepoDeleteEvent, @@ -60,14 +60,14 @@ from rhodecode.events.repo import ( # n RepoPrePullEvent, RepoPullEvent, ) -from rhodecode.events.repo_group import ( # noqa +from rhodecode.events.repo_group import ( # pragma: no cover RepoGroupEvent, RepoGroupCreateEvent, RepoGroupUpdateEvent, RepoGroupDeleteEvent, ) -from rhodecode.events.pullrequest import ( # noqa +from rhodecode.events.pullrequest import ( # pragma: no cover PullRequestEvent, PullRequestCreateEvent, PullRequestUpdateEvent, diff --git a/rhodecode/integrations/registry.py b/rhodecode/integrations/registry.py --- a/rhodecode/integrations/registry.py +++ b/rhodecode/integrations/registry.py @@ -32,6 +32,6 @@ class IntegrationTypeRegistry(collection if key in self: log.debug( 'Overriding existing integration type %s (%s) with %s', - self[key], key, IntegrationType) + self[key].__class__, key, IntegrationType) self[key] = IntegrationType diff --git a/rhodecode/integrations/types/hipchat.py b/rhodecode/integrations/types/hipchat.py --- a/rhodecode/integrations/types/hipchat.py +++ b/rhodecode/integrations/types/hipchat.py @@ -36,6 +36,26 @@ from rhodecode.integrations.types.base i log = logging.getLogger(__name__) +REPO_PUSH_TEMPLATE = Template(''' +${data['actor']['username']} pushed to repo ${data['repo']['repo_name']}: +
+
    +%for branch, branch_commits in branches_commits.items(): +
  • + % if branch: + branch: ${branch_commits['branch']['name']} + % else: + to trunk + % endif +
      + % for commit in branch_commits['commits']: +
    • ${commit['short_id']} - ${commit['message_html']}
    • + % endfor +
    +
  • +%endfor +''') + class HipchatSettingsSchema(colander.Schema): color_choices = [ @@ -76,27 +96,6 @@ class HipchatSettingsSchema(colander.Sch ) -repo_push_template = Template(''' -${data['actor']['username']} pushed to repo ${data['repo']['repo_name']}: -
    -
      -%for branch, branch_commits in branches_commits.items(): -
    • - % if branch: - branch: ${branch_commits['branch']['name']} - % else: - to trunk - % endif -
        - % for commit in branch_commits['commits']: -
      • ${commit['short_id']} - ${commit['message_html']}
      • - % endfor -
      -
    • -%endfor -''') - - class HipchatIntegrationType(IntegrationTypeBase, CommitParsingDataHandler): key = 'hipchat' display_name = _('Hipchat') @@ -226,7 +225,7 @@ class HipchatIntegrationType(Integration data['push']['branches'], data['push']['commits']) result = render_with_traceback( - repo_push_template, + REPO_PUSH_TEMPLATE, data=data, branches_commits=branches_commits, ) diff --git a/rhodecode/integrations/types/slack.py b/rhodecode/integrations/types/slack.py --- a/rhodecode/integrations/types/slack.py +++ b/rhodecode/integrations/types/slack.py @@ -41,6 +41,30 @@ from rhodecode.integrations.types.base i log = logging.getLogger(__name__) +def html_to_slack_links(message): + return re.compile(r'(.+?)').sub( + r'<\1|\2>', message) + + +REPO_PUSH_TEMPLATE = Template(''' +<% + def branch_text(branch): + if branch: + return 'on branch: <{}|{}>'.format(branch_commits['branch']['url'], branch_commits['branch']['name']) + else: + ## case for SVN no branch push... + return 'to trunk' +%> \ + +% for branch, branch_commits in branches_commits.items(): +${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} ${branch_text(branch)} +% for commit in branch_commits['commits']: +`<${commit['url']}|${commit['short_id']}>` - ${commit['message_html']|html_to_slack_links} +% endfor +% endfor +''') + + class SlackSettingsSchema(colander.Schema): service = colander.SchemaNode( colander.String(), @@ -258,7 +282,6 @@ class SlackIntegrationType(IntegrationTy return title, text def format_repo_push_event(self, data): - branches_commits = self.aggregate_branch_data( data['push']['branches'], data['push']['commits']) @@ -267,25 +290,8 @@ class SlackIntegrationType(IntegrationTy ''') title = render_with_traceback(template, data=data) - repo_push_template = Template(textwrap.dedent(r''' - <% - def branch_text(branch): - if branch: - return 'on branch: <{}|{}>'.format(branch_commits['branch']['url'], branch_commits['branch']['name']) - else: - ## case for SVN no branch push... - return 'to trunk' - %> \ - % for branch, branch_commits in branches_commits.items(): - ${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} ${branch_text(branch)} - % for commit in branch_commits['commits']: - `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html']|html_to_slack_links} - % endfor - % endfor - ''')) - text = render_with_traceback( - repo_push_template, + REPO_PUSH_TEMPLATE, data=data, branches_commits=branches_commits, html_to_slack_links=html_to_slack_links, @@ -308,11 +314,6 @@ class SlackIntegrationType(IntegrationTy return title, text -def html_to_slack_links(message): - return re.compile(r'(.+?)').sub( - r'<\1|\2>', message) - - @async_task(ignore_result=True, base=RequestContextTask) def post_text_to_slack(settings, title, text, fields=None, overrides=None): log.debug('sending %s (%s) to slack %s', title, text, settings['service']) diff --git a/rhodecode/integrations/views.py b/rhodecode/integrations/views.py --- a/rhodecode/integrations/views.py +++ b/rhodecode/integrations/views.py @@ -25,9 +25,9 @@ import webhelpers.paginate from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound +from rhodecode.integrations import integration_type_registry from rhodecode.apps._base import BaseAppView -from rhodecode.integrations import integration_type_registry -from rhodecode.apps.admin.navigation import navigation_list +from rhodecode.apps._base.navigation import navigation_list from rhodecode.lib.auth import ( LoginRequired, CSRFRequired, HasPermissionAnyDecorator, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator) diff --git a/rhodecode/lib/bleach_whitelist.py b/rhodecode/lib/bleach_whitelist.py --- a/rhodecode/lib/bleach_whitelist.py +++ b/rhodecode/lib/bleach_whitelist.py @@ -60,6 +60,7 @@ markdown_tags = [ "table", "thead", "tbody", "tfoot", "tr", "th", "td", "img", "a", + "input", ] markdown_attrs = { @@ -68,7 +69,8 @@ markdown_attrs = { "a": ["href", "alt", "title", "name"], "abbr": ["title"], "acronym": ["title"], - "pre": ["lang"] + "pre": ["lang"], + "input": ["type", "disabled"] } standard_styles = [ diff --git a/rhodecode/lib/celerylib/loader.py b/rhodecode/lib/celerylib/loader.py --- a/rhodecode/lib/celerylib/loader.py +++ b/rhodecode/lib/celerylib/loader.py @@ -33,7 +33,7 @@ import importlib from celery import Celery from celery import signals from celery import Task -from celery import exceptions # noqa +from celery import exceptions # pragma: no cover from kombu.serialization import register from pyramid.threadlocal import get_current_request diff --git a/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py --- a/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py +++ b/rhodecode/lib/dbmigrate/schema/db_4_11_0_0.py @@ -40,12 +40,12 @@ from sqlalchemy import ( Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary, Text, Float, PickleType) from sqlalchemy.sql.expression import true, false -from sqlalchemy.sql.functions import coalesce, count # noqa +from sqlalchemy.sql.functions import coalesce, count # pragma: no cover from sqlalchemy.orm import ( relationship, joinedload, class_mapper, validates, aliased) from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.exc import IntegrityError # noqa +from sqlalchemy.exc import IntegrityError # pragma: no cover from sqlalchemy.dialects.mysql import LONGTEXT from beaker.cache import cache_region from zope.cachedescriptors.property import Lazy as LazyProperty diff --git a/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py --- a/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py +++ b/rhodecode/lib/dbmigrate/schema/db_4_13_0_0.py @@ -40,12 +40,12 @@ from sqlalchemy import ( Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary, Text, Float, PickleType) from sqlalchemy.sql.expression import true, false -from sqlalchemy.sql.functions import coalesce, count # noqa +from sqlalchemy.sql.functions import coalesce, count # pragma: no cover from sqlalchemy.orm import ( relationship, joinedload, class_mapper, validates, aliased) from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.exc import IntegrityError # noqa +from sqlalchemy.exc import IntegrityError # pragma: no cover from sqlalchemy.dialects.mysql import LONGTEXT from beaker.cache import cache_region from zope.cachedescriptors.property import Lazy as LazyProperty diff --git a/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py b/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py --- a/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py +++ b/rhodecode/lib/dbmigrate/schema/db_4_9_0_0.py @@ -41,7 +41,7 @@ from sqlalchemy.ext.hybrid import hybrid from sqlalchemy.orm import ( relationship, joinedload, class_mapper, validates, aliased) from sqlalchemy.sql.expression import true -from sqlalchemy.sql.functions import coalesce, count # noqa +from sqlalchemy.sql.functions import coalesce, count # pragma: no cover from beaker.cache import cache_region from zope.cachedescriptors.property import Lazy as LazyProperty diff --git a/rhodecode/lib/diff_match_patch.py b/rhodecode/lib/diff_match_patch.py --- a/rhodecode/lib/diff_match_patch.py +++ b/rhodecode/lib/diff_match_patch.py @@ -95,7 +95,7 @@ class diff_match_patch: Array of changes. """ # Set a deadline by which time the diff must be complete. - if deadline == None: + if deadline is None: # Unlike in most languages, Python counts time in seconds. if self.Diff_Timeout <= 0: deadline = sys.maxint @@ -103,7 +103,7 @@ class diff_match_patch: deadline = time.time() + self.Diff_Timeout # Check for null inputs. - if text1 == None or text2 == None: + if text1 is None or text2 is None: raise ValueError("Null inputs. (diff_main)") # Check for equality (speedup). @@ -1227,7 +1227,7 @@ class diff_match_patch: Best match index or -1. """ # Check for null inputs. - if text == None or pattern == None: + if text is None or pattern is None: raise ValueError("Null inputs. (match_main)") loc = max(0, min(loc, len(text))) diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -1018,7 +1018,7 @@ def style_metatag(tag_type, value): return html_value -def bool2icon(value): +def bool2icon(value, show_at_false=True): """ Returns boolean value of a given value, represented as html element with classes that will represent icons @@ -1029,8 +1029,9 @@ def bool2icon(value): if value: # does bool conversion return HTML.tag('i', class_="icon-true") else: # not true as bool - return HTML.tag('i', class_="icon-false") - + if show_at_false: + return HTML.tag('i', class_="icon-false") + return HTML.tag('i') #============================================================================== # PERMS diff --git a/rhodecode/lib/index/whoosh.py b/rhodecode/lib/index/whoosh.py --- a/rhodecode/lib/index/whoosh.py +++ b/rhodecode/lib/index/whoosh.py @@ -264,7 +264,7 @@ class WhooshResultWrapper(object): # inside hit object, and we don't need all res = dict(hit) - f_path = '' # noqa + f_path = '' # pragma: no cover if self.search_type in ['content', 'path']: f_path = res['path'][len(res['repository']):] f_path = f_path.lstrip(os.sep) diff --git a/rhodecode/lib/logging_formatter.py b/rhodecode/lib/logging_formatter.py --- a/rhodecode/lib/logging_formatter.py +++ b/rhodecode/lib/logging_formatter.py @@ -126,7 +126,8 @@ class ColorFormatter(ExceptionAwareForma def _inject_req_id(record): from pyramid.threadlocal import get_current_request req = get_current_request() - req_id = 'req_id:%-36s ' % (getattr(req, 'req_id', None)) + dummy = '00000000-0000-0000-0000-000000000000' + req_id = 'req_id:%-36s' % (getattr(req, 'req_id', dummy)) record.req_id = req_id diff --git a/rhodecode/lib/markdown_ext.py b/rhodecode/lib/markdown_ext.py --- a/rhodecode/lib/markdown_ext.py +++ b/rhodecode/lib/markdown_ext.py @@ -22,36 +22,7 @@ import re import markdown -from mdx_gfm import GithubFlavoredMarkdownExtension # noqa - - -class FlavoredCheckboxExtension(markdown.Extension): - - def extendMarkdown(self, md, md_globals): - md.preprocessors.add('checklist', - FlavoredCheckboxPreprocessor(md), 'unescape') - - -class FlavoredCheckboxPreprocessor(markdown.preprocessors.Preprocessor): - """ - Replaces occurrences of [ ] or [x] to checkbox input - """ - - pattern = re.compile(r'^([*-]) \[([ x])\]') - - def run(self, lines): - return [self._transform_line(line) for line in lines] - - def _transform_line(self, line): - return self.pattern.sub(self._replacer, line) - - def _replacer(self, match): - list_prefix, state = match.groups() - checked = '' if state == ' ' else ' checked="checked"' - return '%s ' % (list_prefix, - checked) +from mdx_gfm import GithubFlavoredMarkdownExtension # pragma: no cover class FlavoredCheckboxPostprocessor(markdown.postprocessors.Postprocessor): diff --git a/rhodecode/lib/markup_renderer.py b/rhodecode/lib/markup_renderer.py --- a/rhodecode/lib/markup_renderer.py +++ b/rhodecode/lib/markup_renderer.py @@ -40,8 +40,7 @@ from docutils.writers import html4css1 import markdown from rhodecode.lib.markdown_ext import GithubFlavoredMarkdownExtension -from rhodecode.lib.utils2 import ( - safe_str, safe_unicode, md5_safe, MENTIONS_REGEX) +from rhodecode.lib.utils2 import (safe_unicode, md5_safe, MENTIONS_REGEX) log = logging.getLogger(__name__) @@ -172,6 +171,32 @@ def relative_path(path, request_path, is return u'/' + final_path +_cached_markdown_renderer = None + + +def get_markdown_renderer(extensions, output_format): + global _cached_markdown_renderer + + if _cached_markdown_renderer is None: + _cached_markdown_renderer = markdown.Markdown( + extensions=extensions, + enable_attributes=False, output_format=output_format) + return _cached_markdown_renderer + + +_cached_markdown_renderer_flavored = None + + +def get_markdown_renderer_flavored(extensions, output_format): + global _cached_markdown_renderer_flavored + + if _cached_markdown_renderer_flavored is None: + _cached_markdown_renderer_flavored = markdown.Markdown( + extensions=extensions + [GithubFlavoredMarkdownExtension()], + enable_attributes=False, output_format=output_format) + return _cached_markdown_renderer_flavored + + class MarkupRenderer(object): RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw'] @@ -183,14 +208,10 @@ class MarkupRenderer(object): URL_PAT = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]' r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)') - extensions = ['codehilite', 'extra', 'def_list', 'sane_lists'] + extensions = ['markdown.extensions.codehilite', 'markdown.extensions.extra', + 'markdown.extensions.def_list', 'markdown.extensions.sane_lists'] + output_format = 'html4' - markdown_renderer = markdown.Markdown( - extensions, enable_attributes=False, output_format=output_format) - - markdown_renderer_flavored = markdown.Markdown( - extensions + [GithubFlavoredMarkdownExtension()], - enable_attributes=False, output_format=output_format) # extension together with weights. Lower is first means we control how # extensions are attached to readme names with those. @@ -346,9 +367,11 @@ class MarkupRenderer(object): """ if flavored: - markdown_renderer = cls.markdown_renderer_flavored + markdown_renderer = get_markdown_renderer_flavored( + cls.extensions, cls.output_format) else: - markdown_renderer = cls.markdown_renderer + markdown_renderer = get_markdown_renderer( + cls.extensions, cls.output_format) if mentions: mention_pat = re.compile(MENTIONS_REGEX) @@ -452,7 +475,7 @@ class MarkupRenderer(object): return nb, resources - def _sanitize_resources(resources): + def _sanitize_resources(input_resources): """ Skip/sanitize some of the CSS generated and included in jupyter so it doesn't messes up UI so much @@ -464,8 +487,8 @@ class MarkupRenderer(object): # _default_template_path_default, to achieve that # strip the reset CSS - resources[0] = resources[0][resources[0].find('/*! Source'):] - return resources + input_resources[0] = input_resources[0][input_resources[0].find('/*! Source'):] + return input_resources def as_html(notebook): conf = Config() diff --git a/rhodecode/lib/middleware/simplehg.py b/rhodecode/lib/middleware/simplehg.py --- a/rhodecode/lib/middleware/simplehg.py +++ b/rhodecode/lib/middleware/simplehg.py @@ -69,6 +69,11 @@ class SimpleHg(simplevcs.SimpleVCS): 'statlfile': 'pull', 'lheads': 'pull', + # evolve + 'evoext_obshashrange_v1': 'pull', + 'evoext_obshash': 'pull', + 'evoext_obshash1': 'pull', + 'unbundle': 'push', 'pushkey': 'push', } diff --git a/rhodecode/lib/pyramid_utils.py b/rhodecode/lib/pyramid_utils.py --- a/rhodecode/lib/pyramid_utils.py +++ b/rhodecode/lib/pyramid_utils.py @@ -20,7 +20,7 @@ import os from pyramid.compat import configparser -from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # noqa +from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # pragma: no cover from pyramid.scripting import prepare from rhodecode.lib.request import Request @@ -37,6 +37,23 @@ def get_app_config(ini_path): return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd()) +class BootstrappedRequest(Request): + """ + Special version of Request Which has some available methods like in pyramid. + Some code (used for template rendering) requires this, and we unsure it's present. + """ + + def translate(self, msg): + return msg + + def plularize(self, singular, plural, n): + return singular + + def get_partial_renderer(self, tmpl_name): + from rhodecode.lib.partial_renderer import get_partial_renderer + return get_partial_renderer(request=self, tmpl_name=tmpl_name) + + def bootstrap(config_uri, request=None, options=None, env=None): if env: os.environ.update(env) @@ -48,7 +65,7 @@ def bootstrap(config_uri, request=None, except (configparser.NoSectionError, configparser.NoOptionError): pass - request = request or Request.blank('/', base_url=base_url) + request = request or BootstrappedRequest.blank('/', base_url=base_url) return pyramid_bootstrap(config_uri, request=request, options=options) diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py --- a/rhodecode/lib/vcs/backends/base.py +++ b/rhodecode/lib/vcs/backends/base.py @@ -50,8 +50,8 @@ from rhodecode.lib.vcs.exceptions import log = logging.getLogger(__name__) -FILEMODE_DEFAULT = 0100644 -FILEMODE_EXECUTABLE = 0100755 +FILEMODE_DEFAULT = 0o100644 +FILEMODE_EXECUTABLE = 0o100755 Reference = collections.namedtuple('Reference', ('type', 'name', 'commit_id')) MergeResponse = collections.namedtuple( @@ -209,7 +209,7 @@ class BaseRepository(object): def get_create_shadow_cache_pr_path(self, db_repo): path = db_repo.cached_diffs_dir if not os.path.exists(path): - os.makedirs(path, 0755) + os.makedirs(path, 0o755) return path @classmethod @@ -942,13 +942,13 @@ class BaseCommit(object): """ raise NotImplementedError - def get_file_commit(self, path, pre_load=None): + def get_path_commit(self, path, pre_load=None): """ Returns last commit of the file at the given `path`. :param pre_load: Optional. List of commit attributes to load. """ - commits = self.get_file_history(path, limit=1, pre_load=pre_load) + commits = self.get_path_history(path, limit=1, pre_load=pre_load) if not commits: raise RepositoryError( 'Failed to fetch history for path {}. ' @@ -956,7 +956,7 @@ class BaseCommit(object): path)) return commits[0] - def get_file_history(self, path, limit=None, pre_load=None): + def get_path_history(self, path, limit=None, pre_load=None): """ Returns history of file as reversed list of :class:`BaseCommit` objects for which file at given `path` has been modified. @@ -1046,7 +1046,7 @@ class BaseCommit(object): ('tags', ','.join(self.tags)), ] meta = ["%s:%s" % (f_name, value) for f_name, value in metadata] - file_info.append(('.archival.txt', 0644, False, '\n'.join(meta))) + file_info.append(('.archival.txt', 0o644, False, '\n'.join(meta))) connection.Hg.archive_repo(file_path, mtime, file_info, kind) @@ -1194,8 +1194,8 @@ class BaseCommit(object): self.idx = value def get_file_changeset(self, path): - warnings.warn("Use get_file_commit instead", DeprecationWarning) - return self.get_file_commit(path) + warnings.warn("Use get_path_commit instead", DeprecationWarning) + return self.get_path_commit(path) class BaseChangesetClass(type): @@ -1502,7 +1502,7 @@ class EmptyCommit(BaseCommit): def id(self): return self.raw_id - def get_file_commit(self, path): + def get_path_commit(self, path): return self def get_file_content(self, path): diff --git a/rhodecode/lib/vcs/backends/git/commit.py b/rhodecode/lib/vcs/backends/git/commit.py --- a/rhodecode/lib/vcs/backends/git/commit.py +++ b/rhodecode/lib/vcs/backends/git/commit.py @@ -298,7 +298,7 @@ class GitCommit(base.BaseCommit): id_, _ = self._get_id_for_path(path) return self._remote.blob_raw_length(id_) - def get_file_history(self, path, limit=None, pre_load=None): + def get_path_history(self, path, limit=None, pre_load=None): """ Returns history of file as reversed list of `GitCommit` objects for which file at given `path` has been modified. @@ -321,21 +321,6 @@ class GitCommit(base.BaseCommit): self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in commit_ids] - # TODO: unused for now potential replacement for subprocess - def get_file_history_2(self, path, limit=None, pre_load=None): - """ - Returns history of file as reversed list of `Commit` objects for - which file at given `path` has been modified. - """ - self._get_filectx(path) - f_path = safe_str(path) - - commit_ids = self._remote.get_file_history(f_path, self.id, limit) - - return [ - self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) - for commit_id in commit_ids] - def get_file_annotate(self, path, pre_load=None): """ Returns a generator of four element tuples with diff --git a/rhodecode/lib/vcs/backends/git/repository.py b/rhodecode/lib/vcs/backends/git/repository.py --- a/rhodecode/lib/vcs/backends/git/repository.py +++ b/rhodecode/lib/vcs/backends/git/repository.py @@ -161,7 +161,7 @@ class GitRepository(BaseRepository): GitRepository.check_url(src_url, self.config) if create: - os.makedirs(self.path, mode=0755) + os.makedirs(self.path, mode=0o755) if bare: self._remote.init_bare() diff --git a/rhodecode/lib/vcs/backends/hg/commit.py b/rhodecode/lib/vcs/backends/hg/commit.py --- a/rhodecode/lib/vcs/backends/hg/commit.py +++ b/rhodecode/lib/vcs/backends/hg/commit.py @@ -252,13 +252,13 @@ class MercurialCommit(base.BaseCommit): path = self._get_filectx(path) return self._remote.fctx_size(self.idx, path) - def get_file_history(self, path, limit=None, pre_load=None): + def get_path_history(self, path, limit=None, pre_load=None): """ Returns history of file as reversed list of `MercurialCommit` objects for which file at given ``path`` has been modified. """ path = self._get_filectx(path) - hist = self._remote.file_history(self.idx, path, limit) + hist = self._remote.node_history(self.idx, path, limit) return [ self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in hist] diff --git a/rhodecode/lib/vcs/backends/hg/repository.py b/rhodecode/lib/vcs/backends/hg/repository.py --- a/rhodecode/lib/vcs/backends/hg/repository.py +++ b/rhodecode/lib/vcs/backends/hg/repository.py @@ -355,7 +355,7 @@ class MercurialRepository(BaseRepository create = False if create: - os.makedirs(self.path, mode=0755) + os.makedirs(self.path, mode=0o755) self._remote.localrepository(create) diff --git a/rhodecode/lib/vcs/backends/svn/commit.py b/rhodecode/lib/vcs/backends/svn/commit.py --- a/rhodecode/lib/vcs/backends/svn/commit.py +++ b/rhodecode/lib/vcs/backends/svn/commit.py @@ -121,7 +121,7 @@ class SubversionCommit(base.BaseCommit): path = self._fix_path(path) return self._remote.get_file_size(safe_str(path), self._svn_rev) - def get_file_history(self, path, limit=None, pre_load=None): + def get_path_history(self, path, limit=None, pre_load=None): path = safe_str(self._fix_path(path)) history = self._remote.node_history(path, self._svn_rev, limit) return [ diff --git a/rhodecode/lib/vcs/geventcurl.py b/rhodecode/lib/vcs/geventcurl.py --- a/rhodecode/lib/vcs/geventcurl.py +++ b/rhodecode/lib/vcs/geventcurl.py @@ -31,7 +31,7 @@ import greenlet # Import everything from pycurl. # This allows us to use this module as a drop in replacement of pycurl. -from pycurl import * # noqa +from pycurl import * # pragma: no cover from gevent import core from gevent.hub import Waiter diff --git a/rhodecode/lib/vcs/nodes.py b/rhodecode/lib/vcs/nodes.py --- a/rhodecode/lib/vcs/nodes.py +++ b/rhodecode/lib/vcs/nodes.py @@ -404,7 +404,7 @@ class FileNode(Node): def last_commit(self): if self.commit: pre_load = ["author", "date", "message"] - return self.commit.get_file_commit(self.path, pre_load=pre_load) + return self.commit.get_path_commit(self.path, pre_load=pre_load) raise NodeError( "Cannot retrieve last commit of the file without " "related commit attribute") @@ -510,7 +510,7 @@ class FileNode(Node): """ if self.commit is None: raise NodeError('Unable to get commit for this FileNode') - return self.commit.get_file_history(self.path) + return self.commit.get_path_history(self.path) @LazyProperty def annotate(self): @@ -724,6 +724,15 @@ class DirNode(Node): return size + @LazyProperty + def last_commit(self): + if self.commit: + pre_load = ["author", "date", "message"] + return self.commit.get_path_commit(self.path, pre_load=pre_load) + raise NodeError( + "Cannot retrieve last commit of the file without " + "related commit attribute") + def __repr__(self): return '<%s %r @ %s>' % (self.__class__.__name__, self.path, getattr(self.commit, 'short_id', '')) diff --git a/rhodecode/model/__init__.py b/rhodecode/model/__init__.py --- a/rhodecode/model/__init__.py +++ b/rhodecode/model/__init__.py @@ -21,6 +21,7 @@ import logging +import rhodecode from rhodecode.model import meta, db from rhodecode.lib.utils2 import obfuscate_url_pw, get_encryption_key @@ -36,7 +37,7 @@ def init_model(engine, encryption_key=No :param engine: engine to bind to """ engine_str = obfuscate_url_pw(str(engine.url)) - log.info("initializing db for %s", engine_str) + log.info("RhodeCode %s initializing db for %s", rhodecode.__version__, engine_str) meta.Base.metadata.bind = engine db.ENCRYPTION_KEY = encryption_key diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -40,12 +40,12 @@ from sqlalchemy import ( Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary, Text, Float, PickleType) from sqlalchemy.sql.expression import true, false -from sqlalchemy.sql.functions import coalesce, count # noqa +from sqlalchemy.sql.functions import coalesce, count # pragma: no cover from sqlalchemy.orm import ( relationship, joinedload, class_mapper, validates, aliased) from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.exc import IntegrityError # noqa +from sqlalchemy.exc import IntegrityError # pragma: no cover from sqlalchemy.dialects.mysql import LONGTEXT from zope.cachedescriptors.property import Lazy as LazyProperty @@ -377,6 +377,12 @@ class RhodeCodeSetting(Base, BaseModel): % (self.SETTINGS_TYPES.keys(), val)) self._app_settings_type = val + @classmethod + def get_by_prefix(cls, prefix): + return RhodeCodeSetting.query()\ + .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\ + .all() + def __unicode__(self): return u"<%s('%s:%s[%s]')>" % ( self.__class__.__name__, @@ -4143,20 +4149,16 @@ class ExternalIdentity(Base, BaseModel): base_table_args ) - external_id = Column('external_id', Unicode(255), default=u'', - primary_key=True) + external_id = Column('external_id', Unicode(255), default=u'', primary_key=True) external_username = Column('external_username', Unicode(1024), default=u'') - local_user_id = Column('local_user_id', Integer(), - ForeignKey('users.user_id'), primary_key=True) - provider_name = Column('provider_name', Unicode(255), default=u'', - primary_key=True) + local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) + provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True) access_token = Column('access_token', String(1024), default=u'') alt_token = Column('alt_token', String(1024), default=u'') token_secret = Column('token_secret', String(1024), default=u'') @classmethod - def by_external_id_and_provider(cls, external_id, provider_name, - local_user_id=None): + def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None): """ Returns ExternalIdentity instance based on search params @@ -4198,6 +4200,13 @@ class ExternalIdentity(Base, BaseModel): query = query.filter(cls.local_user_id == local_user_id) return query + @classmethod + def load_provider_plugin(cls, plugin_id): + from rhodecode.authentication.base import loadplugin + _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id) + auth_plugin = loadplugin(_plugin_id) + return auth_plugin + class Integration(Base, BaseModel): __tablename__ = 'integrations' diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py --- a/rhodecode/model/notification.py +++ b/rhodecode/model/notification.py @@ -95,15 +95,14 @@ class NotificationModel(BaseModel): log.debug('sending notifications %s to admins: %s', notification_type, recipients_objs) else: - recipients_objs = [] + recipients_objs = set() for u in recipients: obj = self._get_user(u) if obj: - recipients_objs.append(obj) + recipients_objs.add(obj) else: # we didn't find this user, log the error and carry on log.error('cannot notify unknown user %r', u) - recipients_objs = set(recipients_objs) if not recipients_objs: raise Exception('no valid recipients specified') @@ -122,7 +121,7 @@ class NotificationModel(BaseModel): return notification # don't send email to person who created this comment - rec_objs = set(recipients_objs).difference(set([created_by_obj])) + rec_objs = set(recipients_objs).difference({created_by_obj}) # now notify all recipients in question diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -1576,10 +1576,8 @@ class PullRequestModel(BaseModel): from rc_reviewers.utils import get_default_reviewers_data from rc_reviewers.utils import validate_default_reviewers except ImportError: - from rhodecode.apps.repository.utils import \ - get_default_reviewers_data - from rhodecode.apps.repository.utils import \ - validate_default_reviewers + from rhodecode.apps.repository.utils import get_default_reviewers_data + from rhodecode.apps.repository.utils import validate_default_reviewers return get_default_reviewers_data, validate_default_reviewers diff --git a/rhodecode/model/repo_group.py b/rhodecode/model/repo_group.py --- a/rhodecode/model/repo_group.py +++ b/rhodecode/model/repo_group.py @@ -181,7 +181,7 @@ class RepoGroupModel(BaseModel): self.check_exist_filesystem(group_name) create_path = os.path.join(self.repos_path, group_name) log.debug('creating new group in %s', create_path) - os.makedirs(create_path, mode=0755) + os.makedirs(create_path, mode=0o755) log.debug('created group in %s', create_path) def _rename_group(self, old, new): diff --git a/rhodecode/model/settings.py b/rhodecode/model/settings.py --- a/rhodecode/model/settings.py +++ b/rhodecode/model/settings.py @@ -582,7 +582,6 @@ class VcsSettingsModel(object): self._create_or_update_ui( self.repo_settings, *phases, value=safe_str(data[phases_key])) - def create_or_update_global_hg_settings(self, data): largefiles, largefiles_store, phases, hgsubversion, evolve \ = self.GLOBAL_HG_SETTINGS diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py --- a/rhodecode/model/user.py +++ b/rhodecode/model/user.py @@ -344,8 +344,8 @@ class UserModel(BaseModel): new_user.is_new_user = not edit # for users that didn's specify auth type, we use RhodeCode built in from rhodecode.authentication.plugins import auth_rhodecode - extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.name - extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.name + extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid + extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid try: new_user.username = username @@ -392,14 +392,15 @@ class UserModel(BaseModel): log.error(traceback.format_exc()) raise - def create_registration(self, form_data): + def create_registration(self, form_data, + extern_name='rhodecode', extern_type='rhodecode'): from rhodecode.model.notification import NotificationModel from rhodecode.model.notification import EmailNotificationModel try: form_data['admin'] = False - form_data['extern_name'] = 'rhodecode' - form_data['extern_type'] = 'rhodecode' + form_data['extern_name'] = extern_name + form_data['extern_type'] = extern_type new_user = self.create(form_data) self.sa.add(new_user) diff --git a/rhodecode/model/validation_schema/__init__.py b/rhodecode/model/validation_schema/__init__.py --- a/rhodecode/model/validation_schema/__init__.py +++ b/rhodecode/model/validation_schema/__init__.py @@ -20,5 +20,5 @@ import colander -from colander import Invalid # noqa, don't remove this +from colander import Invalid # pragma: no cover diff --git a/rhodecode/public/502.html b/rhodecode/public/502.html --- a/rhodecode/public/502.html +++ b/rhodecode/public/502.html @@ -27,10 +27,7 @@ padding-left: 10px; } li { - list-style-type: none; - } - li:before { - content: "\2014\00A0"; + list-style-type: disc; } .error_message { font-weight: normal; @@ -89,7 +86,9 @@

      - 502 Bad Gateway | Backend server is unreachable + 502 Bad Gateway +
      + Backend server is unreachable

      Possible Causes

      diff --git a/rhodecode/public/css/buttons.less b/rhodecode/public/css/buttons.less --- a/rhodecode/public/css/buttons.less +++ b/rhodecode/public/css/buttons.less @@ -254,7 +254,7 @@ input[type="button"] { .btn-social { &:extend(.btn-default); margin: 5px 5px 5px 0px; - min-width: 150px; + min-width: 160px; } // TODO: johbo: check these exceptions diff --git a/rhodecode/public/css/code-block.less b/rhodecode/public/css/code-block.less --- a/rhodecode/public/css/code-block.less +++ b/rhodecode/public/css/code-block.less @@ -570,14 +570,17 @@ div.annotatediv { margin-left: 2px; marg .code { display: block; border:0px !important; } .code-highlight, /* TODO: dan: merge codehilite into code-highlight */ +/* This can be generated with `pygmentize -S default -f html` */ .codehilite { .hll { background-color: #ffffcc } .c { color: #408080; font-style: italic } /* Comment */ .err, .codehilite .err { border: none } /* Error */ .k { color: #008000; font-weight: bold } /* Keyword */ .o { color: #666666 } /* Operator */ + .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ .cm { color: #408080; font-style: italic } /* Comment.Multiline */ .cp { color: #BC7A00 } /* Comment.Preproc */ + .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ .c1 { color: #408080; font-style: italic } /* Comment.Single */ .cs { color: #408080; font-style: italic } /* Comment.Special */ .gd { color: #A00000 } /* Generic.Deleted */ @@ -585,11 +588,11 @@ div.annotatediv { margin-left: 2px; marg .gr { color: #FF0000 } /* Generic.Error */ .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .gi { color: #00A000 } /* Generic.Inserted */ - .go { color: #808080 } /* Generic.Output */ + .go { color: #888888 } /* Generic.Output */ .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ .gs { font-weight: bold } /* Generic.Strong */ .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ - .gt { color: #0040D0 } /* Generic.Traceback */ + .gt { color: #0044DD } /* Generic.Traceback */ .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ @@ -612,12 +615,15 @@ div.annotatediv { margin-left: 2px; marg .nv { color: #19177C } /* Name.Variable */ .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ .w { color: #bbbbbb } /* Text.Whitespace */ + .mb { color: #666666 } /* Literal.Number.Bin */ .mf { color: #666666 } /* Literal.Number.Float */ .mh { color: #666666 } /* Literal.Number.Hex */ .mi { color: #666666 } /* Literal.Number.Integer */ .mo { color: #666666 } /* Literal.Number.Oct */ + .sa { color: #BA2121 } /* Literal.String.Affix */ .sb { color: #BA2121 } /* Literal.String.Backtick */ .sc { color: #BA2121 } /* Literal.String.Char */ + .dl { color: #BA2121 } /* Literal.String.Delimiter */ .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ .s2 { color: #BA2121 } /* Literal.String.Double */ .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ @@ -628,9 +634,11 @@ div.annotatediv { margin-left: 2px; marg .s1 { color: #BA2121 } /* Literal.String.Single */ .ss { color: #19177C } /* Literal.String.Symbol */ .bp { color: #008000 } /* Name.Builtin.Pseudo */ + .fm { color: #0000FF } /* Name.Function.Magic */ .vc { color: #19177C } /* Name.Variable.Class */ .vg { color: #19177C } /* Name.Variable.Global */ .vi { color: #19177C } /* Name.Variable.Instance */ + .vm { color: #19177C } /* Name.Variable.Magic */ .il { color: #666666 } /* Literal.Number.Integer.Long */ } diff --git a/rhodecode/public/css/comments.less b/rhodecode/public/css/comments.less --- a/rhodecode/public/css/comments.less +++ b/rhodecode/public/css/comments.less @@ -548,10 +548,9 @@ form.comment-form { } .nav-links li { display: inline-block; + list-style-type: none; } -.nav-links li:before { - content: ""; -} + .nav-links li a.disabled { cursor: not-allowed; } diff --git a/rhodecode/public/css/forms.less b/rhodecode/public/css/forms.less --- a/rhodecode/public/css/forms.less +++ b/rhodecode/public/css/forms.less @@ -219,7 +219,6 @@ form.rcform { li { list-style-type: none; - &:before { content:none; } &:after { content: ""; float: left; diff --git a/rhodecode/public/css/helpers.less b/rhodecode/public/css/helpers.less --- a/rhodecode/public/css/helpers.less +++ b/rhodecode/public/css/helpers.less @@ -43,7 +43,9 @@ a { cursor: pointer; } float: right; clear: right; - li:before { content:none; } + li { + list-style-type: none; + } } //--- DEVICE-SPECIFIC CLASSES ---------------// diff --git a/rhodecode/public/css/legacy_code_styles.less b/rhodecode/public/css/legacy_code_styles.less --- a/rhodecode/public/css/legacy_code_styles.less +++ b/rhodecode/public/css/legacy_code_styles.less @@ -126,6 +126,11 @@ div.markdown-block h6 { overflow: visible !important; } +div.markdown-block h1, +div.markdown-block h2 { + border-bottom: 1px #e6e5e5 solid !important; +} + div.markdown-block h1 { font-size: 32px; margin: 15px 0 15px 0 !important; @@ -135,7 +140,6 @@ div.markdown-block h1 { div.markdown-block h2 { font-size: 24px !important; margin: 34px 0 10px 0 !important; - border-top: 3px #e6e5e5 solid !important; padding-top: 15px !important; padding-bottom: 8px !important; } @@ -199,6 +203,7 @@ div.markdown-block pre { div.markdown-block img { border-style: none; background-color: #fff; + padding-right: 20px; } @@ -317,9 +322,13 @@ div.rst-block h3 { margin: 1em 0 !important; } +div.rst-block h1, +div.rst-block h2 { + border-bottom: 1px #e6e5e5 solid !important; +} + div.rst-block h2 { margin-top: 1.5em !important; - border-top: 4px solid #e0e0e0 !important; padding-top: .5em !important; } diff --git a/rhodecode/public/css/main-content.less b/rhodecode/public/css/main-content.less --- a/rhodecode/public/css/main-content.less +++ b/rhodecode/public/css/main-content.less @@ -196,6 +196,16 @@ .text-as-placeholder { padding-top: @input-padding-px + @border-thickness-inputs; } + + .no-border { + border: 1px; + } + + .no-horizontal-padding { + padding-left: 0; + padding-right: 0; + } + } // TODO: johbo: Try to find a better integration of this bit. diff --git a/rhodecode/public/css/main.less b/rhodecode/public/css/main.less --- a/rhodecode/public/css/main.less +++ b/rhodecode/public/css/main.less @@ -315,7 +315,6 @@ ul.auth_plugins { margin-right: @padding; } - &:before { content: none; } } } @@ -1325,6 +1324,7 @@ table.integrations { position: relative; width: 100%; padding-bottom: 8px; + list-style-type: none; } .reviewer_entry { @@ -1719,8 +1719,8 @@ BIN_FILENODE = 7 padding: 0px 0px; } -.pull-request-merge li:before{ - content:none; +.pull-request-merge li { + list-style-type: none; } .pull-request-merge .pull-request-wrap { @@ -1957,7 +1957,7 @@ BIN_FILENODE = 7 font-size: @journal-fontsize; line-height: 1em; - &:before { content: none; } + list-style-type: none; } } } diff --git a/rhodecode/public/css/navigation.less b/rhodecode/public/css/navigation.less --- a/rhodecode/public/css/navigation.less +++ b/rhodecode/public/css/navigation.less @@ -201,8 +201,7 @@ padding: 0 2px; } } - - &:before { content: none; } + list-style-type: none; } > li { @@ -305,8 +304,7 @@ line-height: 1em; color: @grey3; background-color: @grey6; - - &:before { content: none; } + list-style-type: none; a { display: block; @@ -398,8 +396,6 @@ line-height: 1em; list-style-type: none; - &:before { content: none; } - a { display: block; height: 16px; @@ -461,8 +457,6 @@ line-height: 1em; color: @grey6; - &:before { content: none; } - &>.select2-result-label { padding: 8px 0; border-bottom: @border-thickness solid @grey3; @@ -494,8 +488,7 @@ line-height: 1em; font-family: @text-light; color: @grey2; - - &:before { content: none; } + list-style-type: none; &:hover { background-color: @grey3; @@ -520,8 +513,7 @@ ul#context-pages { li { line-height: 1em; - - &:before { content: none; } + list-style-type: none; a { color: @grey3; @@ -622,6 +614,7 @@ ul#context-pages { padding-bottom: @menupadding; line-height: 1em; color: @grey4; + list-style-type: none; &.active a { color: @grey2; @@ -630,8 +623,6 @@ ul#context-pages { a { color: @grey4; } - - &:before { content: none; } } } diff --git a/rhodecode/public/css/readme-box.less b/rhodecode/public/css/readme-box.less --- a/rhodecode/public/css/readme-box.less +++ b/rhodecode/public/css/readme-box.less @@ -17,6 +17,11 @@ div.readme_box h6 { overflow: visible !important; } +div.readme_box h1, +div.readme_box h2 { + border-bottom: 1px #e6e5e5 solid !important; +} + div.readme_box h1 { font-size: 32px; margin: 15px 0 15px 0 !important; @@ -26,7 +31,6 @@ div.readme_box h1 { div.readme_box h2 { font-size: 24px !important; margin: 34px 0 10px 0 !important; - border-top: 3px #e6e5e5 solid !important; padding-top: 15px !important; padding-bottom: 8px !important; } @@ -90,6 +94,7 @@ div.readme_box pre { div.readme_box img { border-style: none; background-color: #fff; + padding-right: 20px; } @@ -107,7 +112,7 @@ div.readme_box ol { div.readme_box ul li, div.readme_box ol li { - list-style: bullet !important; + list-style: disc !important; margin: 6px !important; padding: 0 !important; } diff --git a/rhodecode/public/css/select2.less b/rhodecode/public/css/select2.less --- a/rhodecode/public/css/select2.less +++ b/rhodecode/public/css/select2.less @@ -174,8 +174,6 @@ select.select2{height:28px;visibility:hi line-height: 1em; list-style-type: none; - &:before { content: none; } - &:hover, &.select2-highlighted { background-color: @rclightblue; diff --git a/rhodecode/public/css/summary.less b/rhodecode/public/css/summary.less --- a/rhodecode/public/css/summary.less +++ b/rhodecode/public/css/summary.less @@ -104,11 +104,7 @@ padding-left: 0; li { - - &:before { - content: none; - width: 0; - } + list-style-type: none; } } } diff --git a/rhodecode/public/css/tables.less b/rhodecode/public/css/tables.less --- a/rhodecode/public/css/tables.less +++ b/rhodecode/public/css/tables.less @@ -64,7 +64,9 @@ table.dataTable { .expired td { background-color: @grey7; } - + .inactive td { + background-color: @grey6; + } th { text-align: left; font-weight: @text-semibold-weight; diff --git a/rhodecode/public/css/tags.less b/rhodecode/public/css/tags.less --- a/rhodecode/public/css/tags.less +++ b/rhodecode/public/css/tags.less @@ -49,8 +49,6 @@ margin: 0 0 @padding; line-height: 1em; list-style-type: none; - - &:before { content: none; } } } diff --git a/rhodecode/public/css/type.less b/rhodecode/public/css/type.less --- a/rhodecode/public/css/type.less +++ b/rhodecode/public/css/type.less @@ -274,8 +274,11 @@ mark, list-style: none; text-align: right; - li:before { content: none; } - li { float: right; } + li { + float: right; + list-style-type: none; + } + a { display: inline-block; margin-left: @textmargin/2; @@ -338,15 +341,7 @@ li { ul li { position: relative; - display: block; - list-style-type: none; - - &:before { - content: "\2014\00A0"; - position: absolute; - top: 0; - left: -1.25em; - } + list-style-type: disc; p:first-child { display:inline; @@ -539,7 +534,7 @@ address { color: @grey4; font-family: @text-light; &.pre-formatting { - white-space: pre; + white-space: pre-wrap; } } diff --git a/rhodecode/public/css/variables.less b/rhodecode/public/css/variables.less --- a/rhodecode/public/css/variables.less +++ b/rhodecode/public/css/variables.less @@ -125,7 +125,7 @@ @label-summary-minwidth: 80px; @search-form-width: 400px; @fields-input-m: 400px; -@fields-input-l: 800px; +@fields-input-l: 720px; // BUTTONS @button-padding: .9em; diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -14,7 +14,6 @@ function registerRCRoutes() { // routes registration pyroutes.register('favicon', '/favicon.ico', []); pyroutes.register('robots', '/robots.txt', []); - pyroutes.register('auth_home', '/_admin/auth*traverse', []); pyroutes.register('global_integrations_new', '/_admin/integrations/new', []); pyroutes.register('global_integrations_home', '/_admin/integrations', []); pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']); @@ -30,6 +29,7 @@ function registerRCRoutes() { pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']); pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']); pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); + pyroutes.register('auth_home', '/_admin/auth*traverse', []); pyroutes.register('ops_ping', '/_admin/ops/ping', []); pyroutes.register('ops_error_test', '/_admin/ops/error', []); pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []); diff --git a/rhodecode/rcserver.py b/rhodecode/rcserver.py --- a/rhodecode/rcserver.py +++ b/rhodecode/rcserver.py @@ -475,11 +475,10 @@ class RcServerCommand(object): msg = '' self.out('Exiting%s (-v to see traceback)' % msg) - def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover return loadapp(app_spec, name=name, relative_to=relative_to, **kw) - def loadserver(self, server_spec, name, relative_to, **kw): # pragma:no cover + def loadserver(self, server_spec, name, relative_to, **kw): # pragma: no cover return loadserver( server_spec, name=name, relative_to=relative_to, **kw) diff --git a/rhodecode/templates/admin/auth/auth_settings.mako b/rhodecode/templates/admin/auth/auth_settings.mako --- a/rhodecode/templates/admin/auth/auth_settings.mako +++ b/rhodecode/templates/admin/auth/auth_settings.mako @@ -39,48 +39,56 @@
      ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)} -
      -

      ${_("Enabled and Available Plugins")}

      -
      +
      -
      -
      ${_("Enabled Plugins")}
      + +
      ${_("Ordered Activated Plugins")}
      - ${h.textarea('auth_plugins',cols=23,rows=5,class_="medium")} + ${h.textarea('auth_plugins',cols=120,rows=20,class_="medium")}
      -

      ${_('List of plugins, separated by commas.' +

      +

      ${_('List of plugins, separated by commas.' '\nThe order of the plugins is also the order in which ' - 'RhodeCode Enterprise will try to authenticate a user.')}

      -
      + 'RhodeCode Enterprise will try to authenticate a user.')} +

      +
      -
      -
      ${_('Available Built-in Plugins')}
      -
        - %for plugin in available_plugins: -
      • -
        - - ${_('enabled') if plugin.get_id() in enabled_plugins else _('disabled')} - - ${plugin.get_display_name()} (${plugin.get_id()}) -
        -
      • - %endfor -
      -
      + + + + + + + %for plugin in available_plugins: + + + + + + + + %endfor +
      ${_('Activate')}${_('Plugin Name')}${_('Documentation')}${_('Plugin ID')}${_('Enabled')}
      + + ${_('activated') if plugin.get_id() in enabled_plugins else _('not active')} + + ${plugin.get_display_name()} + % if plugin.docs(): + docs + % endif + ${plugin.get_id()}${h.bool2icon(plugin.is_active(),show_at_false=False)}
      ${h.submit('save',_('Save'),class_="btn")}
      -
      ${h.end_form()}
      @@ -103,15 +111,15 @@ elems.splice(elems.indexOf(plugin_id), 1); auth_plugins_input.val(elems.join(',\n')); $(cur_button).removeClass('btn-success'); - cur_button.innerHTML = _gettext('disabled'); + cur_button.innerHTML = _gettext('not active'); } else{ - if(elems.indexOf(plugin_id) == -1){ - elems.push(plugin_id); + if (elems.indexOf(plugin_id) === -1) { + elems.push(plugin_id); } auth_plugins_input.val(elems.join(',\n')); $(cur_button).addClass('btn-success'); - cur_button.innerHTML = _gettext('enabled'); + cur_button.innerHTML = _gettext('activated'); } }); diff --git a/rhodecode/templates/admin/auth/plugin_settings.mako b/rhodecode/templates/admin/auth/plugin_settings.mako --- a/rhodecode/templates/admin/auth/plugin_settings.mako +++ b/rhodecode/templates/admin/auth/plugin_settings.mako @@ -51,23 +51,29 @@
      %for node in plugin.get_settings_schema(): - <% label_css_class = ("label-checkbox" if (node.widget == "bool") else "") %> + <% + label_to_type = {'label-checkbox': 'bool', 'label-textarea': 'textarea'} + %> +
      -
      +
      %if node.widget in ["string", "int", "unicode"]: - ${h.text(node.name, defaults.get(node.name), class_="medium")} + ${h.text(node.name, defaults.get(node.name), class_="large")} %elif node.widget == "password": - ${h.password(node.name, defaults.get(node.name), class_="medium")} + ${h.password(node.name, defaults.get(node.name), class_="large")} %elif node.widget == "bool":
      ${h.checkbox(node.name, True, checked=defaults.get(node.name))}
      %elif node.widget == "select": - ${h.select(node.name, defaults.get(node.name), node.validator.choices)} + ${h.select(node.name, defaults.get(node.name), node.validator.choices, class_="select2AuthSetting")} + %elif node.widget == "textarea": +
      ${h.textarea(node.name, defaults.get(node.name), rows=10)}
      %elif node.widget == "readonly": ${node.default} %else: This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select]. %endif + %if node.name in errors: ${errors.get(node.name)}
      @@ -91,6 +97,18 @@ ${h.end_form()}
      + +% if request.GET.get('schema'): +## this is for development and creation of example configurations for documentation +
      +    % for node in plugin.get_settings_schema():
      +    *option*: `${node.name}` => `${defaults.get(node.name)}`${'\n    # '.join(['']+node.description.splitlines())}
      +
      +    % endfor
      +
      + +% endif +
      @@ -98,8 +116,7 @@ -## TODO: Ugly hack to get ldap select elements to work. -## Find a solution to integrate this nicely. + diff --git a/rhodecode/templates/admin/my_account/my_account.mako b/rhodecode/templates/admin/my_account/my_account.mako --- a/rhodecode/templates/admin/my_account/my_account.mako +++ b/rhodecode/templates/admin/my_account/my_account.mako @@ -32,10 +32,10 @@
    • ${_('SSH Keys')}
    • ${_('User Group Membership')}
    • - ## TODO: Find a better integration of oauth views into navigation. - <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %> - % if my_account_oauth_url: -
    • ${_('OAuth Identities')}
    • + ## TODO: Find a better integration of oauth/saml views into navigation. + <% my_account_external_url = h.route_path_or_none('my_account_external_identity') %> + % if my_account_external_url: +
    • ${_('External Identities')}
    • % endif
    • ${_('Emails')}
    • ${_('Repositories')}
    • diff --git a/rhodecode/templates/admin/permissions/permissions_auth_token_access.mako b/rhodecode/templates/admin/permissions/permissions_auth_token_access.mako --- a/rhodecode/templates/admin/permissions/permissions_auth_token_access.mako +++ b/rhodecode/templates/admin/permissions/permissions_auth_token_access.mako @@ -49,9 +49,7 @@ Currently the following views are set: % for route_name, view_fqn, view_url, active in c.view_data: - % if active: - ${h.bool2icon(active)} - % endif + ${h.bool2icon(active, show_at_false=False)} ${view_fqn} ${view_url} diff --git a/rhodecode/templates/admin/settings/settings_global.mako b/rhodecode/templates/admin/settings/settings_global.mako --- a/rhodecode/templates/admin/settings/settings_global.mako +++ b/rhodecode/templates/admin/settings/settings_global.mako @@ -102,6 +102,7 @@ +
      @@ -212,7 +213,7 @@ // This can be used to send a global maintenance messages or other // important messages to all users of the RhodeCode Enterprise system. -$(document).ready(function(e){ +$(document).ready(function(e) { // EDIT - put your message below var message = "TYPE YOUR MESSAGE HERE"; @@ -248,6 +249,32 @@ + + + + + + - ${self.title()} @@ -46,6 +43,8 @@ c.template_context['default_user'] = { ${self.robots()} + + ## CSS definitions <%def name="css()"> diff --git a/rhodecode/templates/errors/error_document.mako b/rhodecode/templates/errors/error_document.mako --- a/rhodecode/templates/errors/error_document.mako +++ b/rhodecode/templates/errors/error_document.mako @@ -5,13 +5,16 @@ Error - ${c.error_message} - %if c.redirect_time: %endif + + + +

+ ${'This is a notification from RhodeCode. %(instance_url)s' % {'instance_url': instance_url}} +