{"action":"created","comment":{"url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/comments/27536328","id":27536328,"diff_hunk":"@@ -0,0 +1,276 @@\n+\"\"\"ACME protocol v02 messages.\"\"\"\n+from letsencrypt.acme import challenges\n+from letsencrypt.acme import fields\n+from letsencrypt.acme import jose\n+\n+\n+class Error(jose.JSONObjectWithFields, Exception):\n+    \"\"\"ACME error.\n+\n+    https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00\n+\n+    \"\"\"\n+\n+    ERROR_TYPE_NAMESPACE = 'urn:acme:error:'\n+    ERROR_TYPE_DESCRIPTIONS = {\n+        'malformed': 'The request message was malformed',\n+        'unauthorized': 'The client lacks sufficient authorization',\n+        'serverInternal': 'The server experienced an internal error',\n+        'badCSR': 'The CSR is unacceptable (e.g., due to a short key)',\n+    }\n+\n+    # TODO: Boulder omits 'type' and 'instance', spec requires\n+    typ = jose.Field('type', omitempty=True)\n+    title = jose.Field('title', omitempty=True)\n+    detail = jose.Field('detail')\n+    instance = jose.Field('instance', omitempty=True)\n+\n+    @typ.encoder\n+    def typ(value):  # pylint: disable=missing-docstring,no-self-argument\n+        return Error.ERROR_TYPE_NAMESPACE + value\n+\n+    @typ.decoder\n+    def typ(value):  # pylint: disable=missing-docstring,no-self-argument\n+        # pylint thinks isinstance(value, Error), so startswith is not found\n+        # pylint: disable=no-member\n+        if not value.startswith(Error.ERROR_TYPE_NAMESPACE):\n+            raise jose.DeserializationError('Missing error type prefix')\n+\n+        without_prefix = value[len(Error.ERROR_TYPE_NAMESPACE):]\n+        if without_prefix not in Error.ERROR_TYPE_DESCRIPTIONS:\n+            raise jose.DeserializationError('Error type not recognized')\n+\n+        return without_prefix\n+\n+    @property\n+    def description(self):  # pylint: disable=missing-docstring,no-self-argument\n+        return self.ERROR_TYPE_DESCRIPTIONS[self.typ]\n+\n+\n+class _Constant(jose.JSONDeSerializable):\n+    \"\"\"ACME constant.\"\"\"\n+    __slots__ = ('name',)\n+    POSSIBLE_NAMES = NotImplemented\n+\n+    def __init__(self, name):\n+        self.POSSIBLE_NAMES[name] = self\n+        self.name = name\n+\n+    def to_json(self):\n+        return self.name\n+\n+    @classmethod\n+    def from_json(cls, value):\n+        if value not in cls.POSSIBLE_NAMES:\n+            raise jose.DeserializationError(\n+                '{0} not recognized'.format(cls.__name__))\n+        return cls.POSSIBLE_NAMES[value]\n+\n+    def __repr__(self):\n+        return '{0}({1})'.format(self.__class__.__name__, self.name)\n+\n+    def __eq__(self, other):\n+        return isinstance(other, type(self)) and other.name == self.name\n+\n+\n+class Status(_Constant):\n+    \"\"\"ACME \"status\" field.\"\"\"\n+    POSSIBLE_NAMES = {}\n+STATUS_UNKNOWN = Status('unknown')\n+STATUS_PENDING = Status('pending')\n+STATUS_PROCESSING = Status('processing')\n+STATUS_VALID = Status('valid')\n+STATUS_INVALID = Status('invalid')\n+STATUS_REVOKED = Status('revoked')\n+\n+\n+class IdentifierType(_Constant):\n+    \"\"\"ACME identifier type.\"\"\"\n+    POSSIBLE_NAMES = {}\n+IDENTIFIER_FQDN = IdentifierType('dns')  # IdentifierDNS in Boulder\n+\n+\n+class Identifier(jose.JSONObjectWithFields):\n+    \"\"\"ACME identifier.\"\"\"\n+    typ = jose.Field('type', decoder=IdentifierType.from_json)\n+    value = jose.Field('value')\n+\n+\n+class Resource(jose.ImmutableMap):\n+    \"\"\"ACME Resource.\n+\n+    :param body: Resource body.\n+    :type body: Instance of `ResourceBody` (subclass).\n+\n+    :param str uri: Location of the resource.\n+\n+    \"\"\"\n+    __slots__ = ('body', 'uri')\n+\n+\n+class ResourceBody(jose.JSONObjectWithFields):\n+    \"\"\"ACME Resource body.\"\"\"\n+\n+\n+class RegistrationResource(Resource):\n+    \"\"\"Registration resource.\n+\n+    :ivar body: `Registration`\n+    :ivar str uri: URI of the resource.\n+    :ivar new_authzr_uri: URI found in the 'next' Link header\n+\n+    \"\"\"\n+    __slots__ = ('body', 'uri', 'new_authzr_uri', 'terms_of_service')\n+\n+\n+class Registration(ResourceBody):\n+    \"\"\"Registration resource body.\"\"\"\n+\n+    # on new-reg key server ignores 'key' and populates it based on\n+    # JWS.signature.combined.jwk\n+    key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)\n+    contact = jose.Field('contact', omitempty=True, default=())\n+    recovery_token = jose.Field('recoveryToken', omitempty=True)\n+    agreement = jose.Field('agreement', omitempty=True)\n+\n+\n+class ChallengeResource(Resource, jose.JSONObjectWithFields):\n+    \"\"\"Challenge resource.\n+\n+    :ivar body: `.challenges.ChallengeBody`\n+    :ivar authzr_uri: URI found in the 'up' Link header.\n+\n+    \"\"\"\n+    __slots__ = ('body', 'authzr_uri')\n+\n+    @property\n+    def uri(self):  # pylint: disable=missing-docstring,no-self-argument\n+        # bug? 'method already defined line None'\n+        # pylint: disable=function-redefined\n+        return self.body.uri\n+\n+\n+class ChallengeBody(ResourceBody):\n+    \"\"\"Challenge resource body.\n+\n+    Confusingly, this has a similar name to `.challenges.Challenge`, as\n+    well as `.achallanges.AnnotatedChallenge` or\n+    `.achallanges.IndexedChallenge`. Use names such as ``challb`` to\n+    distinguish instances of this class from ``achall`` or ``ichall``.\n+\n+    .. todo::\n+       This class could be integrated with challenges.Challenge, but\n+       this way it would be confusing when compared to acme-spec, where\n+       all challenges are presented without 'uri', 'status', or\n+       'validated' fields.\n+\n+    \"\"\"\n+\n+    __slots__ = ('chall',)\n+    uri = jose.Field('uri')\n+    status = jose.Field('status', decoder=Status.from_json)\n+    validated = fields.RFC3339Field('validated', omitempty=True)\n+\n+    def to_json(self):\n+        jobj = super(ChallengeBody, self).to_json()\n+        jobj.update(self.chall.to_json())\n+        return jobj\n+\n+    @classmethod\n+    def fields_from_json(cls, jobj):\n+        jobj_fields = super(ChallengeBody, cls).fields_from_json(jobj)\n+        jobj_fields['chall'] = challenges.Challenge.from_json(jobj)\n+        return jobj_fields\n+\n+\n+class AuthorizationResource(Resource):\n+    \"\"\"Authorization resource.\n+\n+    :ivar body: `Authorization`\n+    :ivar new_cert_uri: URI found in the 'next' Link header\n+\n+    \"\"\"\n+    __slots__ = ('body', 'uri', 'new_cert_uri')\n+\n+\n+class Authorization(ResourceBody):\n+    \"\"\"Authorization resource body.\n+\n+    :ivar challenges: `list` of `Challenge`\n+\n+    \"\"\"\n+\n+    identifier = jose.Field('identifier', decoder=Identifier.from_json)\n+    challenges = jose.Field('challenges', omitempty=True)\n+    combinations = jose.Field('combinations', omitempty=True)\n+\n+    # TODO: acme-spec #92, #98\n+    key = Registration._fields['key']\n+    contact = Registration._fields['contact']\n+\n+    status = jose.Field('status', omitempty=True, decoder=Status.from_json)\n+    # TODO: 'expires' is allowed for Authorization Resources in\n+    # general, but for Key Authorization '[t]he \"expires\" field MUST\n+    # be absent'... then acme-spec gives example with 'expires'\n+    # present... That's confusing!\n+    expires = fields.RFC3339Field('expires', omitempty=True)\n+\n+    @challenges.decoder\n+    def challenges(value):  # pylint: disable=missing-docstring,no-self-argument\n+        return tuple(ChallengeBody.from_json(chall) for chall in value)\n+\n+    @property\n+    def resolved_combinations(self):\n+        \"\"\"Combinations with challenges instead of indices.\"\"\"\n+        return tuple(tuple(self.challenges[idx] for idx in combo)\n+                     for combo in self.combinations)\n+\n+\n+class CertificateRequest(jose.JSONObjectWithFields):\n+    \"\"\"ACME new-cert request.\n+\n+    :ivar csr: `M2Crypto.X509.Request` wrapped in `.ComparableX509`\n+\n+    \"\"\"\n+    csr = jose.Field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr)\n+    authorizations = jose.Field('authorizations', decoder=tuple)\n+\n+\n+class CertificateResource(Resource):\n+    \"\"\"Authorization resource.\n+\n+    :ivar body: `M2Crypto.X509.X509` wrapped in `.ComparableX509`\n+    :ivar cert_chain_uri: URI found in the 'up' Link header\n+    :ivar authzrs: `list` of `AuthorizationResource`.\n+\n+    \"\"\"\n+    __slots__ = ('body', 'uri', 'cert_chain_uri', 'authzrs')\n+\n+\n+class Revocation(jose.JSONObjectWithFields):\n+    \"\"\"Revocation message.\n+\n+    :ivar revoke: Either a `datetime.datetime` or `NOW`.","path":"letsencrypt/acme/messages2.py","position":253,"original_position":253,"commit_id":"4b829603d0bc6a8edd76825363fbe585efa1497a","original_commit_id":"4b829603d0bc6a8edd76825363fbe585efa1497a","user":{"login":"jdkasten","id":1581662,"avatar_url":"https://avatars.githubusercontent.com/u/1581662?v=3","gravatar_id":"","url":"https://api.github.com/users/jdkasten","html_url":"https://github.com/jdkasten","followers_url":"https://api.github.com/users/jdkasten/followers","following_url":"https://api.github.com/users/jdkasten/following{/other_user}","gists_url":"https://api.github.com/users/jdkasten/gists{/gist_id}","starred_url":"https://api.github.com/users/jdkasten/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdkasten/subscriptions","organizations_url":"https://api.github.com/users/jdkasten/orgs","repos_url":"https://api.github.com/users/jdkasten/repos","events_url":"https://api.github.com/users/jdkasten/events{/privacy}","received_events_url":"https://api.github.com/users/jdkasten/received_events","type":"User","site_admin":false},"body":"might be reasonable to say Revocation.NOW or change Revocation.NOW == 'NOW' just to avoid any confusion.","created_at":"2015-04-01T00:31:14Z","updated_at":"2015-04-01T00:31:14Z","html_url":"https://github.com/letsencrypt/lets-encrypt-preview/pull/325#discussion_r27536328","pull_request_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325","_links":{"self":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/comments/27536328"},"html":{"href":"https://github.com/letsencrypt/lets-encrypt-preview/pull/325#discussion_r27536328"},"pull_request":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325"}}},"pull_request":{"url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325","id":32153853,"html_url":"https://github.com/letsencrypt/lets-encrypt-preview/pull/325","diff_url":"https://github.com/letsencrypt/lets-encrypt-preview/pull/325.diff","patch_url":"https://github.com/letsencrypt/lets-encrypt-preview/pull/325.patch","issue_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/issues/325","number":325,"state":"open","locked":false,"title":"Restified messages2 and network2","user":{"login":"kuba","id":106176,"avatar_url":"https://avatars.githubusercontent.com/u/106176?v=3","gravatar_id":"","url":"https://api.github.com/users/kuba","html_url":"https://github.com/kuba","followers_url":"https://api.github.com/users/kuba/followers","following_url":"https://api.github.com/users/kuba/following{/other_user}","gists_url":"https://api.github.com/users/kuba/gists{/gist_id}","starred_url":"https://api.github.com/users/kuba/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kuba/subscriptions","organizations_url":"https://api.github.com/users/kuba/orgs","repos_url":"https://api.github.com/users/kuba/repos","events_url":"https://api.github.com/users/kuba/events{/privacy}","received_events_url":"https://api.github.com/users/kuba/received_events","type":"User","site_admin":false},"body":"Now with full coverage and lint compliance. There is a lot of space for improvement, but I believe it's ready to get merged to `master`. I will fix TODOs in follow-up PRs.\r\n\r\nSorry once again for the number of commits, hope you don't mind :*","created_at":"2015-03-27T23:41:06Z","updated_at":"2015-04-01T00:31:14Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ffe381bcc53c413cca5f2be43df29aceb62944db","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325/commits","review_comments_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325/comments","review_comment_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/comments{/number}","comments_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/issues/325/comments","statuses_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/statuses/4b829603d0bc6a8edd76825363fbe585efa1497a","head":{"label":"kuba:network2","ref":"network2","sha":"4b829603d0bc6a8edd76825363fbe585efa1497a","user":{"login":"kuba","id":106176,"avatar_url":"https://avatars.githubusercontent.com/u/106176?v=3","gravatar_id":"","url":"https://api.github.com/users/kuba","html_url":"https://github.com/kuba","followers_url":"https://api.github.com/users/kuba/followers","following_url":"https://api.github.com/users/kuba/following{/other_user}","gists_url":"https://api.github.com/users/kuba/gists{/gist_id}","starred_url":"https://api.github.com/users/kuba/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kuba/subscriptions","organizations_url":"https://api.github.com/users/kuba/orgs","repos_url":"https://api.github.com/users/kuba/repos","events_url":"https://api.github.com/users/kuba/events{/privacy}","received_events_url":"https://api.github.com/users/kuba/received_events","type":"User","site_admin":false},"repo":{"id":26831060,"name":"lets-encrypt-preview","full_name":"kuba/lets-encrypt-preview","owner":{"login":"kuba","id":106176,"avatar_url":"https://avatars.githubusercontent.com/u/106176?v=3","gravatar_id":"","url":"https://api.github.com/users/kuba","html_url":"https://github.com/kuba","followers_url":"https://api.github.com/users/kuba/followers","following_url":"https://api.github.com/users/kuba/following{/other_user}","gists_url":"https://api.github.com/users/kuba/gists{/gist_id}","starred_url":"https://api.github.com/users/kuba/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kuba/subscriptions","organizations_url":"https://api.github.com/users/kuba/orgs","repos_url":"https://api.github.com/users/kuba/repos","events_url":"https://api.github.com/users/kuba/events{/privacy}","received_events_url":"https://api.github.com/users/kuba/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kuba/lets-encrypt-preview","description":"An ACME client that can update Apache configurations (developer preview) ","fork":true,"url":"https://api.github.com/repos/kuba/lets-encrypt-preview","forks_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/forks","keys_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/teams","hooks_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/hooks","issue_events_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/issues/events{/number}","events_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/events","assignees_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/assignees{/user}","branches_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/branches{/branch}","tags_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/tags","blobs_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/git/refs{/sha}","trees_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/statuses/{sha}","languages_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/languages","stargazers_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/stargazers","contributors_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/contributors","subscribers_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/subscribers","subscription_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/subscription","commits_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/commits{/sha}","git_commits_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/git/commits{/sha}","comments_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/comments{/number}","issue_comment_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/issues/comments{/number}","contents_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/contents/{+path}","compare_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/merges","archive_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/downloads","issues_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/issues{/number}","pulls_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/pulls{/number}","milestones_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/milestones{/number}","notifications_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/labels{/name}","releases_url":"https://api.github.com/repos/kuba/lets-encrypt-preview/releases{/id}","created_at":"2014-11-18T21:34:04Z","updated_at":"2015-03-30T12:36:13Z","pushed_at":"2015-03-30T13:23:49Z","git_url":"git://github.com/kuba/lets-encrypt-preview.git","ssh_url":"git@github.com:kuba/lets-encrypt-preview.git","clone_url":"https://github.com/kuba/lets-encrypt-preview.git","svn_url":"https://github.com/kuba/lets-encrypt-preview","homepage":null,"size":3670,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"letsencrypt:master","ref":"master","sha":"60a52943f69ccdc99e0572d7b2c7cdfce472a70b","user":{"login":"letsencrypt","id":9289019,"avatar_url":"https://avatars.githubusercontent.com/u/9289019?v=3","gravatar_id":"","url":"https://api.github.com/users/letsencrypt","html_url":"https://github.com/letsencrypt","followers_url":"https://api.github.com/users/letsencrypt/followers","following_url":"https://api.github.com/users/letsencrypt/following{/other_user}","gists_url":"https://api.github.com/users/letsencrypt/gists{/gist_id}","starred_url":"https://api.github.com/users/letsencrypt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/letsencrypt/subscriptions","organizations_url":"https://api.github.com/users/letsencrypt/orgs","repos_url":"https://api.github.com/users/letsencrypt/repos","events_url":"https://api.github.com/users/letsencrypt/events{/privacy}","received_events_url":"https://api.github.com/users/letsencrypt/received_events","type":"Organization","site_admin":false},"repo":{"id":26516210,"name":"lets-encrypt-preview","full_name":"letsencrypt/lets-encrypt-preview","owner":{"login":"letsencrypt","id":9289019,"avatar_url":"https://avatars.githubusercontent.com/u/9289019?v=3","gravatar_id":"","url":"https://api.github.com/users/letsencrypt","html_url":"https://github.com/letsencrypt","followers_url":"https://api.github.com/users/letsencrypt/followers","following_url":"https://api.github.com/users/letsencrypt/following{/other_user}","gists_url":"https://api.github.com/users/letsencrypt/gists{/gist_id}","starred_url":"https://api.github.com/users/letsencrypt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/letsencrypt/subscriptions","organizations_url":"https://api.github.com/users/letsencrypt/orgs","repos_url":"https://api.github.com/users/letsencrypt/repos","events_url":"https://api.github.com/users/letsencrypt/events{/privacy}","received_events_url":"https://api.github.com/users/letsencrypt/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/letsencrypt/lets-encrypt-preview","description":"An ACME client that can update Apache configurations (developer preview) ","fork":false,"url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview","forks_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/forks","keys_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/keys{/key_id}","collaborators_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/teams","hooks_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/hooks","issue_events_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/issues/events{/number}","events_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/events","assignees_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/assignees{/user}","branches_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/branches{/branch}","tags_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/tags","blobs_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/git/refs{/sha}","trees_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/git/trees{/sha}","statuses_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/statuses/{sha}","languages_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/languages","stargazers_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/stargazers","contributors_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/contributors","subscribers_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/subscribers","subscription_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/subscription","commits_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/commits{/sha}","git_commits_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/git/commits{/sha}","comments_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/comments{/number}","issue_comment_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/issues/comments{/number}","contents_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/contents/{+path}","compare_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/compare/{base}...{head}","merges_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/merges","archive_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/downloads","issues_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/issues{/number}","pulls_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls{/number}","milestones_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/milestones{/number}","notifications_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/labels{/name}","releases_url":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/releases{/id}","created_at":"2014-11-12T02:52:20Z","updated_at":"2015-03-31T21:26:16Z","pushed_at":"2015-03-31T18:44:10Z","git_url":"git://github.com/letsencrypt/lets-encrypt-preview.git","ssh_url":"git@github.com:letsencrypt/lets-encrypt-preview.git","clone_url":"https://github.com/letsencrypt/lets-encrypt-preview.git","svn_url":"https://github.com/letsencrypt/lets-encrypt-preview","homepage":null,"size":6309,"stargazers_count":1658,"watchers_count":1658,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":115,"mirror_url":null,"open_issues_count":64,"forks":115,"open_issues":64,"watchers":1658,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325"},"html":{"href":"https://github.com/letsencrypt/lets-encrypt-preview/pull/325"},"issue":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/issues/325"},"comments":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/issues/325/comments"},"review_comments":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325/comments"},"review_comment":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/pulls/325/commits"},"statuses":{"href":"https://api.github.com/repos/letsencrypt/lets-encrypt-preview/statuses/4b829603d0bc6a8edd76825363fbe585efa1497a"}}}}