{"action":"created","comment":{"url":"https://api.github.com/repos/tvs/uaa/pulls/comments/103340066","pull_request_review_id":24120392,"id":103340066,"diff_hunk":"@@ -0,0 +1,335 @@\n+/*******************************************************************************\n+ *     Cloud Foundry\n+ *     Copyright (c) [2017] VMware, Inc. All Rights Reserved.\n+ *\n+ *     This product is licensed to you under the Apache License, Version 2.0 (the \"License\").\n+ *     You may not use this product except in compliance with the License.\n+ *\n+ *     This product includes a number of subcomponents with\n+ *     separate copyright notices and license terms. Your use of these\n+ *     subcomponents is subject to the terms and conditions of the\n+ *     subcomponent's license, as noted in the LICENSE file.\n+ *******************************************************************************/\n+package org.cloudfoundry.identity.uaa.zone;\n+\n+import java.security.NoSuchAlgorithmException;\n+import java.util.ArrayList;\n+import java.util.Collection;\n+import java.util.Collections;\n+import java.util.HashSet;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Set;\n+\n+import org.apache.commons.logging.Log;\n+import org.apache.commons.logging.LogFactory;\n+import org.cloudfoundry.identity.uaa.client.MultitenantClientDetailsService;\n+import org.cloudfoundry.identity.uaa.client.VmidentityDataAccessException;\n+import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants;\n+import org.cloudfoundry.identity.uaa.util.VmidentityUtils;\n+import org.springframework.security.core.GrantedAuthority;\n+import org.springframework.security.core.authority.AuthorityUtils;\n+import org.springframework.security.crypto.password.PasswordEncoder;\n+import org.springframework.security.oauth2.provider.ClientAlreadyExistsException;\n+import org.springframework.security.oauth2.provider.ClientDetails;\n+import org.springframework.security.oauth2.provider.ClientRegistrationException;\n+import org.springframework.security.oauth2.provider.NoSuchClientException;\n+import org.springframework.security.oauth2.provider.client.BaseClientDetails;\n+import org.springframework.util.Assert;\n+\n+import com.google.common.primitives.Ints;\n+import com.vmware.identity.idm.DuplicatedOIDCClientException;\n+import com.vmware.identity.idm.DuplicatedOIDCRedirectURLException;\n+import com.vmware.identity.idm.NoSuchOIDCClientException;\n+import com.vmware.identity.idm.OIDCClient;\n+import com.vmware.identity.idm.client.CasIdmClient;\n+\n+public class MultitenantVmidentityClientDetailsService implements MultitenantClientDetailsService {\n+\n+    private static final Log logger = LogFactory.getLog(MultitenantVmidentityClientDetailsService.class);\n+\n+    private final CasIdmClient idmClient;\n+    private final PasswordEncoder passwordEncoder;\n+\n+    public MultitenantVmidentityClientDetailsService(CasIdmClient idmClient, PasswordEncoder passwordEncoder) throws NoSuchAlgorithmException {\n+        Assert.notNull(idmClient, \"CasIdmClient required\");\n+        this.idmClient = idmClient;\n+        this.passwordEncoder = passwordEncoder;\n+    }\n+\n+    @Override\n+    public void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            String encodedSecret = null;\n+            if (clientDetails.getClientSecret() != null) {\n+                encodedSecret = passwordEncoder.encode(clientDetails.getClientSecret());\n+            }\n+            OIDCClient client = mapClientDetails(clientDetails, encodedSecret);\n+            idmClient.addOIDCClient(tenant, client);\n+        } catch (DuplicatedOIDCClientException e) {\n+            logger.error(\"Client with ID already exists: \" + clientDetails.getClientId(), e);\n+            throw new ClientAlreadyExistsException(\"Client already exists: \" + clientDetails.getClientId());\n+        } catch (DuplicatedOIDCRedirectURLException e) {\n+            logger.error(\"Client with registered redirect already exists: \" + clientDetails.getRegisteredRedirectUri(), e);\n+            throw new ClientAlreadyExistsException(\"Client with registered redirect already exists: \" + clientDetails.getRegisteredRedirectUri());\n+        } catch (Exception e) {\n+            logger.error(\"Unable to add client details\", e);\n+            throw new VmidentityDataAccessException(\"Unable to add client details\");\n+        }\n+\n+    }\n+\n+    @Override\n+    public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            ClientDetails previous = loadClientByClientId(clientDetails.getClientId());\n+            OIDCClient client = mapClientDetails(clientDetails, previous.getClientSecret());\n+            idmClient.setOIDCClient(tenant, client);\n+        } catch (NoSuchOIDCClientException e) {\n+            logger.error(\"No client found with id: \" + clientDetails.getClientId() + \" in identity zone \" + IdentityZoneHolder.get().getName(), e);\n+            throw new NoSuchClientException(\"No client found with id = \" + clientDetails.getClientId() + \" in identity zone \" + IdentityZoneHolder.get().getName());\n+        } catch (Exception e) {\n+            logger.error(\"Unable to update client details with id: \" + clientDetails.getClientId(), e);\n+            throw new VmidentityDataAccessException(\"Unable to update client details\");\n+        }\n+    }\n+\n+    @Override\n+    public void updateClientSecret(String clientId, String secret) throws NoSuchClientException {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            ClientDetails clientDetails = loadClientByClientId(clientId);\n+            OIDCClient client = mapClientDetails(clientDetails, passwordEncoder.encode(secret));\n+            idmClient.setOIDCClient(tenant, client);\n+        } catch (NoSuchOIDCClientException e) {\n+            logger.error(\"No client found with id: \" + clientId + \" in identity zone \" + IdentityZoneHolder.get().getName(), e);\n+            throw new NoSuchClientException(\"No client found with id = \" + clientId + \" in identity zone \" + IdentityZoneHolder.get().getName());\n+        } catch (Exception e) {\n+            logger.error(\"Unable to update client secret with id: \" + clientId, e);\n+            throw new VmidentityDataAccessException(\"Unable to update client secret\");\n+        }\n+    }\n+\n+    @Override\n+    public void removeClientDetails(String clientId) throws NoSuchClientException {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            idmClient.deleteOIDCClient(tenant, clientId);\n+        } catch (NoSuchOIDCClientException e) {\n+            logger.error(\"No client found with id = \" + clientId, e);\n+            throw new NoSuchClientException(\"No client found with id = \" + clientId);\n+        } catch (Exception e) {\n+            logger.error(\"Unable to remove client details with id = \" + clientId, e);\n+            throw new VmidentityDataAccessException(\"Unable to remvoe client details\");\n+        }\n+    }\n+\n+    @Override\n+    public List<ClientDetails> listClientDetails() {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            Collection<OIDCClient> clients = idmClient.getOIDCClients(tenant);\n+\n+            List<ClientDetails> details = new ArrayList<ClientDetails>(clients.size());\n+            for (OIDCClient client : clients) {\n+                details.add(mapOIDCClient(client, tenant, idmClient, passwordEncoder));\n+            }\n+\n+            return details;\n+        } catch (Exception e) {\n+            logger.error(\"Unable to list client details for identity zone: \" + IdentityZoneHolder.get().getName(), e);\n+            throw new VmidentityDataAccessException(\"Unable to list client details\");\n+        }\n+    }\n+\n+    @Override\n+    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {\n+        ClientDetails details;\n+\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            details = mapOIDCClient(idmClient.getOIDCClient(tenant, clientId), tenant, idmClient, passwordEncoder);\n+        } catch (NoSuchOIDCClientException e) {\n+            logger.error(\"Unable to load client with id: \" + clientId, e);\n+            throw new NoSuchClientException(\"No client with requested id: \" + clientId);\n+        } catch (Exception e) {\n+            logger.error(\"An error occurred loading client with id: \" + clientId, e);\n+            throw new VmidentityDataAccessException(\"Unable to load client with id: \" + clientId);\n+        }\n+\n+        return details;\n+    }\n+\n+    @Override\n+    public int deleteByIdentityZone(String zoneId) {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(zoneId, idmClient);\n+            Collection<OIDCClient> clients = idmClient.getOIDCClients(tenant);\n+\n+            for (OIDCClient client : clients) {\n+                idmClient.deleteOIDCClient(tenant, client.getClientId());\n+            }\n+\n+            return clients.size();\n+        } catch (Exception e) {\n+            logger.error(\"Unable to delete clients for identity zone: \" + zoneId, e);\n+            throw new VmidentityDataAccessException(\"Unable to delete clients for identity zone: \" + zoneId);\n+        }\n+    }\n+\n+    @Override\n+    public int deleteByOrigin(String origin, String zoneId) {\n+        return 0;\n+    }\n+\n+    @Override\n+    public Log getLogger() {\n+        return logger;\n+    }\n+\n+    @Override\n+    public int getTotalCount() {\n+        try {\n+            int count = 0;\n+            Collection<String> tenants = idmClient.getAllTenants();\n+\n+            for (String tenant : tenants) {\n+                Collection<OIDCClient> clients = idmClient.getOIDCClients(tenant);\n+                count += clients.size();\n+            }\n+\n+            return count;\n+        } catch (Exception e) {\n+            logger.error(\"Unable to enumerate clients\", e);\n+            throw new VmidentityDataAccessException(\"Unable to enumerate clients\");\n+        }\n+    }\n+\n+    @Override\n+    public void addClientSecret(String clientId, String newSecret) throws NoSuchClientException {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            ClientDetails clientDetails = loadClientByClientId(clientId);\n+\n+            String encodedNewSecret = passwordEncoder.encode(newSecret);\n+            StringBuilder newSecretBuilder = new StringBuilder().append(clientDetails.getClientSecret() == null ? \"\" : clientDetails.getClientSecret() + \" \").append(encodedNewSecret);\n+            OIDCClient client = mapClientDetails(clientDetails, newSecretBuilder.toString());\n+            idmClient.setOIDCClient(tenant, client);\n+        } catch (NoSuchOIDCClientException e) {\n+            logger.error(\"No client found with id: \" + clientId + \" in identity zone \" + IdentityZoneHolder.get().getName(), e);\n+            throw new NoSuchClientException(\"No client found with id = \" + clientId + \" in identity zone \" + IdentityZoneHolder.get().getName());\n+        } catch (Exception e) {\n+            logger.error(\"Unable to add client secret with id: \" + clientId, e);\n+            throw new VmidentityDataAccessException(\"Unable to add client secret\");\n+        }\n+    }\n+\n+    @Override\n+    public void deleteClientSecret(String clientId) throws NoSuchClientException {\n+        try {\n+            String tenant = VmidentityUtils.getTenantName(idmClient);\n+            ClientDetails clientDetails = loadClientByClientId(clientId);\n+            String clientSecret = clientDetails.getClientSecret().split(\" \")[1];\n+            OIDCClient client = mapClientDetails(clientDetails, clientSecret);\n+            idmClient.setOIDCClient(tenant, client);\n+        } catch (NoSuchOIDCClientException e) {\n+            logger.error(\"No client found with id: \" + clientId + \" in identity zone \" + IdentityZoneHolder.get().getName(), e);\n+            throw new NoSuchClientException(\"No client found with id = \" + clientId + \" in identity zone \" + IdentityZoneHolder.get().getName());\n+        } catch (Exception e) {\n+            logger.error(\"Unable t update client secret with id: \" + clientId, e);\n+            throw new VmidentityDataAccessException(\"Unable to update client secret\");\n+        }\n+    }\n+\n+    private static OIDCClient mapClientDetails(ClientDetails details, String clientSecret) {\n+        OIDCClient.Builder builder = new OIDCClient.Builder(details.getClientId());\n+        // It would be better to treat these as 'Collection' types instead...\n+        builder.redirectUris(convertToList(details.getRegisteredRedirectUri()));\n+        builder.resourceIds(convertToList(details.getResourceIds()));\n+        builder.scopes(convertToList(details.getScope()));\n+        builder.authorizedGrantTypes(convertToList(details.getAuthorizedGrantTypes()));\n+        builder.authorities(authoritiesToListOfStrings(details.getAuthorities()));\n+\n+        // builder.accessTokenValiditySeconds(details.getAccessTokenValiditySeconds()); TODO access token validity\n+        // builder.refreshTokenValiditySeconds(details.getRefreshTokenValiditySeconds()); TODO refresh token validity\n+\n+        if (clientSecret != null) {\n+            builder.clientSecret(clientSecret);\n+        }\n+\n+        Set<String> autoApproveScopes = new HashSet<String>();\n+        Map<String, Object> additionalInformation = details.getAdditionalInformation();\n+        Object autoApprovedFromAddInfo = additionalInformation.get(ClientConstants.AUTO_APPROVE);\n+        if (autoApprovedFromAddInfo != null) {\n+           if ((autoApprovedFromAddInfo instanceof Boolean && (Boolean) autoApprovedFromAddInfo || \"true\".equals(autoApprovedFromAddInfo))) {","path":"server/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantVmidentityClientDetailsService.java","position":266,"original_position":266,"commit_id":"982aa51fcdf4e0054ac9cc73b7359b6610641bb0","original_commit_id":"982aa51fcdf4e0054ac9cc73b7359b6610641bb0","user":{"login":"mpotapova-vmw","id":18473138,"avatar_url":"https://avatars.githubusercontent.com/u/18473138?v=3","gravatar_id":"","url":"https://api.github.com/users/mpotapova-vmw","html_url":"https://github.com/mpotapova-vmw","followers_url":"https://api.github.com/users/mpotapova-vmw/followers","following_url":"https://api.github.com/users/mpotapova-vmw/following{/other_user}","gists_url":"https://api.github.com/users/mpotapova-vmw/gists{/gist_id}","starred_url":"https://api.github.com/users/mpotapova-vmw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mpotapova-vmw/subscriptions","organizations_url":"https://api.github.com/users/mpotapova-vmw/orgs","repos_url":"https://api.github.com/users/mpotapova-vmw/repos","events_url":"https://api.github.com/users/mpotapova-vmw/events{/privacy}","received_events_url":"https://api.github.com/users/mpotapova-vmw/received_events","type":"User","site_admin":false},"body":"we have this same logic some other place. probably makes sense to make a function... ","created_at":"2017-02-27T23:27:56Z","updated_at":"2017-02-27T23:34:58Z","html_url":"https://github.com/tvs/uaa/pull/13#discussion_r103340066","pull_request_url":"https://api.github.com/repos/tvs/uaa/pulls/13","_links":{"self":{"href":"https://api.github.com/repos/tvs/uaa/pulls/comments/103340066"},"html":{"href":"https://github.com/tvs/uaa/pull/13#discussion_r103340066"},"pull_request":{"href":"https://api.github.com/repos/tvs/uaa/pulls/13"}}},"pull_request":{"url":"https://api.github.com/repos/tvs/uaa/pulls/13","id":107931043,"html_url":"https://github.com/tvs/uaa/pull/13","diff_url":"https://github.com/tvs/uaa/pull/13.diff","patch_url":"https://github.com/tvs/uaa/pull/13.patch","issue_url":"https://api.github.com/repos/tvs/uaa/issues/13","number":13,"state":"open","locked":false,"title":"Configure UAA to utilize OIDC Clients from IDM","user":{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false},"body":"Configured UAA to utilize OIDC clients from IDM. Requires an additional change to Lightwave to support the new fields (a review has been posted simultaneously).\r\n\r\nRetrieves and stores all information using IDM:\r\n\r\n- Client Secret\r\n- Authorities\r\n- Resource IDs\r\n- Scopes\r\n- Authorized Grant Types\r\n- Auto Approve Scopes\r\n- Additional Information\r\n  - Client Name\r\n  - App Icon\r\n  - App Launch URL\r\n  - `isShowOnHomepage`","created_at":"2017-02-24T23:53:48Z","updated_at":"2017-02-27T23:34:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c1ac8a8c2fba3e15fccc5aacad92e8eb04582f80","assignee":{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false},"assignees":[{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false}],"milestone":{"url":"https://api.github.com/repos/tvs/uaa/milestones/1","html_url":"https://github.com/tvs/uaa/milestone/1","labels_url":"https://api.github.com/repos/tvs/uaa/milestones/1/labels","id":2344407,"number":1,"title":"v1","description":"First milestone of UAA-Lightwave integration","creator":{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false},"open_issues":5,"closed_issues":0,"state":"open","created_at":"2017-02-23T21:34:02Z","updated_at":"2017-02-24T23:53:48Z","due_on":"2017-03-24T07:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/tvs/uaa/pulls/13/commits","review_comments_url":"https://api.github.com/repos/tvs/uaa/pulls/13/comments","review_comment_url":"https://api.github.com/repos/tvs/uaa/pulls/comments{/number}","comments_url":"https://api.github.com/repos/tvs/uaa/issues/13/comments","statuses_url":"https://api.github.com/repos/tvs/uaa/statuses/982aa51fcdf4e0054ac9cc73b7359b6610641bb0","head":{"label":"tvs:features/vmidentityClients","ref":"features/vmidentityClients","sha":"982aa51fcdf4e0054ac9cc73b7359b6610641bb0","user":{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false},"repo":{"id":78046217,"name":"uaa","full_name":"tvs/uaa","owner":{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tvs/uaa","description":"CloudFoundry User Account and Authentication (UAA) Server","fork":true,"url":"https://api.github.com/repos/tvs/uaa","forks_url":"https://api.github.com/repos/tvs/uaa/forks","keys_url":"https://api.github.com/repos/tvs/uaa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tvs/uaa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tvs/uaa/teams","hooks_url":"https://api.github.com/repos/tvs/uaa/hooks","issue_events_url":"https://api.github.com/repos/tvs/uaa/issues/events{/number}","events_url":"https://api.github.com/repos/tvs/uaa/events","assignees_url":"https://api.github.com/repos/tvs/uaa/assignees{/user}","branches_url":"https://api.github.com/repos/tvs/uaa/branches{/branch}","tags_url":"https://api.github.com/repos/tvs/uaa/tags","blobs_url":"https://api.github.com/repos/tvs/uaa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tvs/uaa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tvs/uaa/git/refs{/sha}","trees_url":"https://api.github.com/repos/tvs/uaa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tvs/uaa/statuses/{sha}","languages_url":"https://api.github.com/repos/tvs/uaa/languages","stargazers_url":"https://api.github.com/repos/tvs/uaa/stargazers","contributors_url":"https://api.github.com/repos/tvs/uaa/contributors","subscribers_url":"https://api.github.com/repos/tvs/uaa/subscribers","subscription_url":"https://api.github.com/repos/tvs/uaa/subscription","commits_url":"https://api.github.com/repos/tvs/uaa/commits{/sha}","git_commits_url":"https://api.github.com/repos/tvs/uaa/git/commits{/sha}","comments_url":"https://api.github.com/repos/tvs/uaa/comments{/number}","issue_comment_url":"https://api.github.com/repos/tvs/uaa/issues/comments{/number}","contents_url":"https://api.github.com/repos/tvs/uaa/contents/{+path}","compare_url":"https://api.github.com/repos/tvs/uaa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tvs/uaa/merges","archive_url":"https://api.github.com/repos/tvs/uaa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tvs/uaa/downloads","issues_url":"https://api.github.com/repos/tvs/uaa/issues{/number}","pulls_url":"https://api.github.com/repos/tvs/uaa/pulls{/number}","milestones_url":"https://api.github.com/repos/tvs/uaa/milestones{/number}","notifications_url":"https://api.github.com/repos/tvs/uaa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tvs/uaa/labels{/name}","releases_url":"https://api.github.com/repos/tvs/uaa/releases{/id}","deployments_url":"https://api.github.com/repos/tvs/uaa/deployments","created_at":"2017-01-04T19:29:16Z","updated_at":"2017-02-16T19:27:52Z","pushed_at":"2017-02-24T23:53:48Z","git_url":"git://github.com/tvs/uaa.git","ssh_url":"git@github.com:tvs/uaa.git","clone_url":"https://github.com/tvs/uaa.git","svn_url":"https://github.com/tvs/uaa","homepage":"","size":33886,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":8,"forks":0,"open_issues":8,"watchers":0,"default_branch":"master"}},"base":{"label":"tvs:vmidentity","ref":"vmidentity","sha":"013666bdd098f3bdde7f55eedb081c0f76114285","user":{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false},"repo":{"id":78046217,"name":"uaa","full_name":"tvs/uaa","owner":{"login":"tvs","id":145500,"avatar_url":"https://avatars.githubusercontent.com/u/145500?v=3","gravatar_id":"","url":"https://api.github.com/users/tvs","html_url":"https://github.com/tvs","followers_url":"https://api.github.com/users/tvs/followers","following_url":"https://api.github.com/users/tvs/following{/other_user}","gists_url":"https://api.github.com/users/tvs/gists{/gist_id}","starred_url":"https://api.github.com/users/tvs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvs/subscriptions","organizations_url":"https://api.github.com/users/tvs/orgs","repos_url":"https://api.github.com/users/tvs/repos","events_url":"https://api.github.com/users/tvs/events{/privacy}","received_events_url":"https://api.github.com/users/tvs/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tvs/uaa","description":"CloudFoundry User Account and Authentication (UAA) Server","fork":true,"url":"https://api.github.com/repos/tvs/uaa","forks_url":"https://api.github.com/repos/tvs/uaa/forks","keys_url":"https://api.github.com/repos/tvs/uaa/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tvs/uaa/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tvs/uaa/teams","hooks_url":"https://api.github.com/repos/tvs/uaa/hooks","issue_events_url":"https://api.github.com/repos/tvs/uaa/issues/events{/number}","events_url":"https://api.github.com/repos/tvs/uaa/events","assignees_url":"https://api.github.com/repos/tvs/uaa/assignees{/user}","branches_url":"https://api.github.com/repos/tvs/uaa/branches{/branch}","tags_url":"https://api.github.com/repos/tvs/uaa/tags","blobs_url":"https://api.github.com/repos/tvs/uaa/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tvs/uaa/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tvs/uaa/git/refs{/sha}","trees_url":"https://api.github.com/repos/tvs/uaa/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tvs/uaa/statuses/{sha}","languages_url":"https://api.github.com/repos/tvs/uaa/languages","stargazers_url":"https://api.github.com/repos/tvs/uaa/stargazers","contributors_url":"https://api.github.com/repos/tvs/uaa/contributors","subscribers_url":"https://api.github.com/repos/tvs/uaa/subscribers","subscription_url":"https://api.github.com/repos/tvs/uaa/subscription","commits_url":"https://api.github.com/repos/tvs/uaa/commits{/sha}","git_commits_url":"https://api.github.com/repos/tvs/uaa/git/commits{/sha}","comments_url":"https://api.github.com/repos/tvs/uaa/comments{/number}","issue_comment_url":"https://api.github.com/repos/tvs/uaa/issues/comments{/number}","contents_url":"https://api.github.com/repos/tvs/uaa/contents/{+path}","compare_url":"https://api.github.com/repos/tvs/uaa/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tvs/uaa/merges","archive_url":"https://api.github.com/repos/tvs/uaa/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tvs/uaa/downloads","issues_url":"https://api.github.com/repos/tvs/uaa/issues{/number}","pulls_url":"https://api.github.com/repos/tvs/uaa/pulls{/number}","milestones_url":"https://api.github.com/repos/tvs/uaa/milestones{/number}","notifications_url":"https://api.github.com/repos/tvs/uaa/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tvs/uaa/labels{/name}","releases_url":"https://api.github.com/repos/tvs/uaa/releases{/id}","deployments_url":"https://api.github.com/repos/tvs/uaa/deployments","created_at":"2017-01-04T19:29:16Z","updated_at":"2017-02-16T19:27:52Z","pushed_at":"2017-02-24T23:53:48Z","git_url":"git://github.com/tvs/uaa.git","ssh_url":"git@github.com:tvs/uaa.git","clone_url":"https://github.com/tvs/uaa.git","svn_url":"https://github.com/tvs/uaa","homepage":"","size":33886,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":8,"forks":0,"open_issues":8,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/tvs/uaa/pulls/13"},"html":{"href":"https://github.com/tvs/uaa/pull/13"},"issue":{"href":"https://api.github.com/repos/tvs/uaa/issues/13"},"comments":{"href":"https://api.github.com/repos/tvs/uaa/issues/13/comments"},"review_comments":{"href":"https://api.github.com/repos/tvs/uaa/pulls/13/comments"},"review_comment":{"href":"https://api.github.com/repos/tvs/uaa/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/tvs/uaa/pulls/13/commits"},"statuses":{"href":"https://api.github.com/repos/tvs/uaa/statuses/982aa51fcdf4e0054ac9cc73b7359b6610641bb0"}}}}
{"actor":{"display_login":"mpotapova-vmw"}}