Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting git opt set sll cert locations option in libgit2 #879

Conversation

imbuedhope
Copy link
Contributor

Closes #876

Adds support for the following

import pygit2
pygit2.settings.ssl_cert_file = '/path/to/file'
pygit2.settings.ssl_cert_dir = '/path/to/folder'

Which is the equivalent of the following in C

git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, '/path/to/file', NULL);
git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, '/path/to/folder');

Also adds support for directly loading the settings from the ssl module like follows

import pygit2
import ssl
pygit2.settings._ssl_cert_locations = ssl.get_default_verify_paths()

All the new properties that were added to pygit2.settings.Settings are write only, since it is write only in libgit2.

In addition, pygit2 also attempts sets _ssl_cert_locations based on the ssl module on import. This probably won't work on Windows until the wheels are changed to support this in the libgit2 being shipped with it.

@webknjaz
Copy link
Contributor

webknjaz commented Mar 1, 2019

Oh, I missed this PR...

P.S. s/sll/ssl/ in title

Copy link
Contributor

@webknjaz webknjaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pointed out a few anti-pythonic approaches which should be fixed: pygit2 is a Python wrapper and as such, it shouldn't 100% repeat C-library principles on the high level.

@@ -28,6 +28,9 @@
# Import from the future
from __future__ import absolute_import

# Import from core python modules
from ssl import get_default_verify_paths as default_ssl_verify_paths
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't rename this callable. It's an action, not a constant. So I must start with a verb. get. Renaming it to constant/var-style is harmful and misleading for readability.

Suggested change
from ssl import get_default_verify_paths as default_ssl_verify_paths
from ssl import get_default_verify_paths

try:
# try to set default ssl cert locations
settings._ssl_cert_locations = default_ssl_verify_paths()
except:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't catch all exceptions. Silencing everything makes any related bugs nearly impossible to locate and is harmful therefore. It's important to always fail fast and loudly.
ssl.get_default_verify_paths() shouldn't raise any exceptions anyway.

Suggested change
except:

"""
return option(GIT_OPT_SET_SSL_CERT_LOCATIONS, value, None)

ssl_cert_file = property(fset=__set_ssl_cert_file)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an ancient way of creating properties. Always use it as a decorator instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we should have a getter for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no get support for the ssl cert paths in libgit2. It makes sense to have it added to libgit2 first.

You can not use property as decorator if you don't have a setter since the first param in the __init__ for property is fget.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you can track it on the Python side because it's needed to properly set the other value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can still use a decorator:

@property
def attr(self):
    """A proper docstring."""
    raise AttributeError

@attr.setter
def attr(self, value):
    # set val

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc #879 (comment)

So here's why we need to track the values we set earlier. Semantically, these setters are assigned with the values for what their names mean.
End-users of the library are Pythonistas who always expect the assignment behavior of the attribute to change exactly the thing its name refers to.
So resetting another setting would be misleading and is a side-effect.
That's why we need to track values which we set earlier for one connected setting to use them when setting the other.
Of course, it's not necessary to expose them for reading right now. But it's a least we can do to preserve consistent UX when setting one of TSL cert location sub-options.
As for setting both sub-options, it should be an explicit method because semantically setting a tuple to attribute to side-effect two values is weird.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Digging around, it looks like ultimately libgit2 makes a call to SSL_CTX_load_verify_locations when built with Openssl and the man page for it seems to describe the following behavior.

When looking up CA certificates, the OpenSSL library will first search the certificates in CAfile, then those in CApath.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, that's what I thought...

def __set_ssl_cert_file(self, value):
"""Set the ssl cert file. The value cannot be read.
"""
return option(GIT_OPT_SET_SSL_CERT_LOCATIONS, value, None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setters don't return values.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When setting one value we shouldn't reset another. If a user wants to do this they can update it to None explicitly.

@@ -101,4 +101,30 @@ def cache_object_limit(self, object_type, value):
"""
return option(GIT_OPT_SET_CACHE_OBJECT_LIMIT, object_type, value)

def __set_ssl_cert_file(self, value):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def __set_ssl_cert_file(self, value):
@property
def ssl_cert_file(self):
return self._ssl_cert_file
@ssl_cert_file.setter
def ssl_cert_file(self, value):
"""Set the ssl cert file."""
option(GIT_OPT_SET_SSL_CERT_LOCATIONS, value, self.ssl_cert_dir)
self._ssl_cert_file = value


ssl_cert_dir = property(fset=__set_ssl_cert_dir)

def __set_ssl_cert_locations(self, locations):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one must be a method. Using property here is odd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with properties because all other supported settings are implemented as property. It does not make sense to break consistency.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I wrote earlier, we should go for Pythonic principles. And if they are not respected in some places it doesn't mean that you should follow the lead. When you write in C you don't follow Assembly code-style just because it's an underlying somewhere, do you?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look at the issues this week-end.
But generally speaking, from PEP-8 itself:
Consistency with this style guide is important. Consistency within a project is more important.

Though we don't have a project style guide. Well, will check this week-end.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole paragraph:
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is the most important.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, thank you both for the awesome work you're doing!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule doesn't apply here. Since all other attributes correspond to one value. With two values it doesn't make sense. Setting two values by assigning a tuple to an attribute is ridiculous in Python.

And also when writing Python it doesn't have to look like another language https://www.youtube.com/watch?v=wf-BqAjZb8M. It's fine in the low-level wrapper but not in a public interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I wasn't referring to just PEP8. It's quite a common sense/knowledge. PEP8 is a code style guide but I'm talking about more generic approaches which are probably not formalized explicitly but every Pythonista knows that when they see code looking like Java in .py file something isn't quite right.


try:
# try to set default ssl cert locations
settings._ssl_cert_locations = default_ssl_verify_paths()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this belongs at Settings.__init__(). It is a good practice to have a ready-to-use object after the initializer run.
Extra initializers are popular in C++, not Python. This semantics is untypical for Pythonic projects.

@webknjaz
Copy link
Contributor

webknjaz commented Mar 1, 2019

I still think that setting one value shouldn't reset another one.

@jdavid
Copy link
Member

jdavid commented Mar 2, 2019

Finally looked at the api. I would replace both setters by a single method:

set_ssl_cert_locations(...)

@webknjaz
Copy link
Contributor

webknjaz commented Mar 2, 2019

I'd keep properties for individual suboptions. They are native to Python and is what a typical Pythonista would expect. It better communicates the intent as in "make this setting use that value".

@jdavid
Copy link
Member

jdavid commented Mar 2, 2019

I'd rather not have 2 ways to do the same thing. It's good for the users to have 1 way, so they don't have to stop thinking which one they should use, and code looks the same whoever writes it.

It's also good for the developers/maintainers not to increase the burden: our limited resources are the main constraint. Aiming for feature completeness, api consistency, and quality documentation is already ambitious. So I'd keep it simple and stick to the 1 way principle (even if the API feels a bit low level, it's not awkward either).

The settings.py file is a good example of the inconsistency we have. It already mixes properties and methods. And a couple of methods even lack the verb at the beginning. caching and cache_max_size should be setters, and cache_object_limit should be renamed to set_cache_object_limit

Then the settings.py file would be all getter/setters except for the 2 not straightforward cases where we would have methods. The advantages: consistency, simplicity, 1 way.

@webknjaz
Copy link
Contributor

webknjaz commented Mar 2, 2019

Yes, there should be one way of doing things. Except for the cases where that way cannot be used. So are you okay with property-based getters/setters then?

@jdavid
Copy link
Member

jdavid commented Mar 2, 2019

Could you summarize the API you propose for ssl cert locations?

@webknjaz
Copy link
Contributor

webknjaz commented Mar 2, 2019

I'll send it as a PR on top of this branch

@webknjaz
Copy link
Contributor

webknjaz commented Mar 2, 2019

Side note: after looking at Settings class I think that this use-case might be a good fit for using attrs library. But it's a topic for a separate discussion.

@webknjaz
Copy link
Contributor

webknjaz commented Mar 2, 2019

@jdavid #884

webknjaz added a commit to webknjaz/pygit2 that referenced this pull request Mar 5, 2019
Usage::

    import pygit2
    pygit2.settings.ssl_cert_file = '/path/to/file'
    pygit2.settings.ssl_cert_dir = '/path/to/folder'
    del pygit2.settings.ssl_cert_file
    pygit2.settings.set_ssl_cert_locations(
        '/path/to/new/file', '/path/to/new/folder',
    )

Co-authored-by: Sriram Raghu <[email protected]>

Closes libgit2#876
Superseeds and closes libgit2#879
webknjaz added a commit to webknjaz/pygit2 that referenced this pull request Mar 5, 2019
Usage::

    import pygit2
    pygit2.settings.ssl_cert_file = '/path/to/file'
    pygit2.settings.ssl_cert_dir = '/path/to/folder'
    del pygit2.settings.ssl_cert_file
    pygit2.settings.set_ssl_cert_locations(
        '/path/to/new/file', '/path/to/new/folder',
    )

Co-authored-by: Sriram Raghu <[email protected]>

Closes libgit2#876
Superseeds and closes libgit2#879
webknjaz added a commit to webknjaz/pygit2 that referenced this pull request Mar 5, 2019
Usage::

    import pygit2
    pygit2.settings.ssl_cert_file = '/path/to/file'
    pygit2.settings.ssl_cert_dir = '/path/to/folder'
    del pygit2.settings.ssl_cert_file
    pygit2.settings.set_ssl_cert_locations(
        '/path/to/new/file', '/path/to/new/folder',
    )

Co-authored-by: Sriram Raghu <[email protected]>

Closes libgit2#876
Superseeds and closes libgit2#879
webknjaz added a commit to webknjaz/pygit2 that referenced this pull request Mar 5, 2019
Usage::

    import pygit2
    pygit2.settings.ssl_cert_file = '/path/to/file'
    pygit2.settings.ssl_cert_dir = '/path/to/folder'
    del pygit2.settings.ssl_cert_file
    pygit2.settings.set_ssl_cert_locations(
        '/path/to/new/file', '/path/to/new/folder',
    )

Co-authored-by: Sriram Raghu <[email protected]>

Closes libgit2#876
Superseeds and closes libgit2#879
webknjaz added a commit to webknjaz/pygit2 that referenced this pull request Mar 5, 2019
Usage::

    import pygit2
    pygit2.settings.ssl_cert_file = '/path/to/file'
    pygit2.settings.ssl_cert_dir = '/path/to/folder'
    del pygit2.settings.ssl_cert_file
    pygit2.settings.set_ssl_cert_locations(
        '/path/to/new/file', '/path/to/new/folder',
    )

Co-authored-by: Sriram Raghu <[email protected]>

Closes libgit2#876
Superseeds and closes libgit2#879
webknjaz added a commit to webknjaz/pygit2 that referenced this pull request Mar 5, 2019
Usage::

    import pygit2
    pygit2.settings.ssl_cert_file = '/path/to/file'
    pygit2.settings.ssl_cert_dir = '/path/to/folder'
    del pygit2.settings.ssl_cert_file
    pygit2.settings.set_ssl_cert_locations(
        '/path/to/new/file', '/path/to/new/folder',
    )

Co-authored-by: Sriram Raghu <[email protected]>

Closes libgit2#876
Superseeds and closes libgit2#879
@jdavid jdavid closed this in #884 Mar 7, 2019
@imbuedhope imbuedhope deleted the 876-Allow-setting-GIT_OPT_SET_SLL_CERT_LOCATIONS-option-in-libgit2 branch March 7, 2019 22:43
netbsd-srcmastr referenced this pull request in NetBSD/pkgsrc Mar 20, 2019
Add test target.

0.28.0 (2019-03-19)
-------------------------

- Upgrade to libgit2 0.28
  `#878 <https://github.com/libgit2/pygit2/issues/878>`_

- Add binary wheels for Linux
  `#793 <https://github.com/libgit2/pygit2/issues/793>`_
  `#869 <https://github.com/libgit2/pygit2/pull/869>`_
  `#874 <https://github.com/libgit2/pygit2/pull/874>`_
  `#875 <https://github.com/libgit2/pygit2/pull/875>`_
  `#883 <https://github.com/libgit2/pygit2/pull/883>`_

- New ``pygit2.Mailmap``, see documentation
  `#804 <https://github.com/libgit2/pygit2/pull/804>`_

- New ``Repository.apply(...)`` wraps ``git_apply(..)``
  `#841 <https://github.com/libgit2/pygit2/issues/841>`_
  `#843 <https://github.com/libgit2/pygit2/pull/843>`_

- Now ``Repository.merge_analysis(...)`` accepts an optional reference parameter
  `#888 <https://github.com/libgit2/pygit2/pull/888>`_
  `#891 <https://github.com/libgit2/pygit2/pull/891>`_

- Now ``Repository.add_worktree(...)`` accepts an optional reference parameter
  `#814 <https://github.com/libgit2/pygit2/issues/814>`_
  `#889 <https://github.com/libgit2/pygit2/pull/889>`_

- Now it's possible to set SSL certificate locations
  `#876 <https://github.com/libgit2/pygit2/issues/876>`_
  `#879 <https://github.com/libgit2/pygit2/pull/879>`_
  `#884 <https://github.com/libgit2/pygit2/pull/884>`_
  `#886 <https://github.com/libgit2/pygit2/pull/886>`_

- Test and documentation improvements
  `#873 <https://github.com/libgit2/pygit2/pull/873>`_
  `#887 <https://github.com/libgit2/pygit2/pull/887>`_

Breaking changes:

- Now ``worktree.path`` returns the path to the worktree directory, not to the
  `.git` file within
  `#803 <https://github.com/libgit2/pygit2/issues/803>`_

- Remove undocumented ``worktree.git_path``
  `#803 <https://github.com/libgit2/pygit2/issues/803>`_


0.27.4 (2019-01-19)
-------------------------

- New ``pygit2.LIBGIT2_VER`` tuple
  `#845 <https://github.com/libgit2/pygit2/issues/845>`_
  `#848 <https://github.com/libgit2/pygit2/pull/848>`_

- New objects now support (in)equality comparison and hash
  `#852 <https://github.com/libgit2/pygit2/issues/852>`_
  `#853 <https://github.com/libgit2/pygit2/pull/853>`_

- New references now support (in)equality comparison
  `#860 <https://github.com/libgit2/pygit2/issues/860>`_
  `#862 <https://github.com/libgit2/pygit2/pull/862>`_

- New ``paths`` optional argument in ``Repository.checkout()``
  `#858 <https://github.com/libgit2/pygit2/issues/858>`_
  `#859 <https://github.com/libgit2/pygit2/pull/859>`_

- Fix speed and windows package regression
  `#849 <https://github.com/libgit2/pygit2/issues/849>`_
  `#857 <https://github.com/libgit2/pygit2/issues/857>`_
  `#851 <https://github.com/libgit2/pygit2/pull/851>`_

- Fix deprecation warning
  `#850 <https://github.com/libgit2/pygit2/pull/850>`_

- Documentation fixes
  `#855 <https://github.com/libgit2/pygit2/pull/855>`_

- Add Python classifiers to setup.py
  `#861 <https://github.com/libgit2/pygit2/pull/861>`_

- Speeding up tests in Travis
  `#854 <https://github.com/libgit2/pygit2/pull/854>`_

Breaking changes:

- Remove deprecated `Reference.get_object()`, use `Reference.peel()` instead


0.27.3 (2018-12-15)
-------------------------

- Move to pytest, drop support for Python 3.3 and cffi 0.x
  `#824 <https://github.com/libgit2/pygit2/issues/824>`_
  `#826 <https://github.com/libgit2/pygit2/pull/826>`_
  `#833 <https://github.com/libgit2/pygit2/pull/833>`_
  `#834 <https://github.com/libgit2/pygit2/pull/834>`_

- New support comparing signatures for (in)equality

- New ``Submodule.head_id``
  `#817 <https://github.com/libgit2/pygit2/pull/817>`_

- New ``Remote.prune(...)``
  `#825 <https://github.com/libgit2/pygit2/pull/825>`_

- New ``pygit2.reference_is_valid_name(...)``
  `#827 <https://github.com/libgit2/pygit2/pull/827>`_

- New ``AlreadyExistsError`` and ``InvalidSpecError``
  `#828 <https://github.com/libgit2/pygit2/issues/828>`_
  `#829 <https://github.com/libgit2/pygit2/pull/829>`_

- New ``Reference.raw_name``, ``Reference.raw_shorthand``, ``Tag.raw_name``,
  ``Tag.raw_message`` and ``DiffFile.raw_path``
  `#840 <https://github.com/libgit2/pygit2/pull/840>`_

- Fix decode error in commit messages and signatures
  `#839 <https://github.com/libgit2/pygit2/issues/839>`_

- Fix, raise error in ``Repository.descendant_of(...)`` if commit doesn't exist
  `#822 <https://github.com/libgit2/pygit2/issues/822>`_
  `#842 <https://github.com/libgit2/pygit2/pull/842>`_

- Documentation fixes
  `#821 <https://github.com/libgit2/pygit2/pull/821>`_

Breaking changes:

- Remove undocumented ``Tag._message``, replaced by ``Tag.raw_message``
EnTeQuAk referenced this pull request in mozilla/addons-server Mar 25, 2019
This PR updates [pygit2](https://pypi.org/project/pygit2) from **0.27.4** to **0.28.0**.



<details>
  <summary>Changelog</summary>
  
  
   ### 0.28.0
   ```
   -------------------------

- Upgrade to libgit2 0.28
  `878 &lt;https://github.com/libgit2/pygit2/issues/878&gt;`_

- Add binary wheels for Linux
  `793 &lt;https://github.com/libgit2/pygit2/issues/793&gt;`_
  `869 &lt;https://github.com/libgit2/pygit2/pull/869&gt;`_
  `874 &lt;https://github.com/libgit2/pygit2/pull/874&gt;`_
  `875 &lt;https://github.com/libgit2/pygit2/pull/875&gt;`_
  `883 &lt;https://github.com/libgit2/pygit2/pull/883&gt;`_

- New ``pygit2.Mailmap``, see documentation
  `804 &lt;https://github.com/libgit2/pygit2/pull/804&gt;`_

- New ``Repository.apply(...)`` wraps ``git_apply(..)``
  `841 &lt;https://github.com/libgit2/pygit2/issues/841&gt;`_
  `843 &lt;https://github.com/libgit2/pygit2/pull/843&gt;`_

- Now ``Repository.merge_analysis(...)`` accepts an optional reference parameter
  `888 &lt;https://github.com/libgit2/pygit2/pull/888&gt;`_
  `891 &lt;https://github.com/libgit2/pygit2/pull/891&gt;`_

- Now ``Repository.add_worktree(...)`` accepts an optional reference parameter
  `814 &lt;https://github.com/libgit2/pygit2/issues/814&gt;`_
  `889 &lt;https://github.com/libgit2/pygit2/pull/889&gt;`_

- Now it&#39;s possible to set SSL certificate locations
  `876 &lt;https://github.com/libgit2/pygit2/issues/876&gt;`_
  `879 &lt;https://github.com/libgit2/pygit2/pull/879&gt;`_
  `884 &lt;https://github.com/libgit2/pygit2/pull/884&gt;`_
  `886 &lt;https://github.com/libgit2/pygit2/pull/886&gt;`_

- Test and documentation improvements
  `873 &lt;https://github.com/libgit2/pygit2/pull/873&gt;`_
  `887 &lt;https://github.com/libgit2/pygit2/pull/887&gt;`_

Breaking changes:

- Now ``worktree.path`` returns the path to the worktree directory, not to the
  `.git` file within
  `803 &lt;https://github.com/libgit2/pygit2/issues/803&gt;`_

- Remove undocumented ``worktree.git_path``
  `803 &lt;https://github.com/libgit2/pygit2/issues/803&gt;`_
   ```
   
  
</details>


 

<details>
  <summary>Links</summary>
  
  - PyPI: https://pypi.org/project/pygit2
  - Changelog: https://pyup.io/changelogs/pygit2/
  - Repo: http://github.com/libgit2/pygit2
</details>
@rcoup
Copy link
Contributor

rcoup commented Feb 24, 2020

Example if you're using certifi:

import certifi
import pygit2
pygit2.settings.ssl_cert_file = certifi.where()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow setting GIT_OPT_SET_SSL_CERT_LOCATIONS option in libgit2
4 participants