1// Copyright 2013 The go-github AUTHORS. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6// apps.go contains functions for accessing data about applications installed
7// on a GitHub organization.
8
9package scrape
10
11import (
12	"bytes"
13	"encoding/json"
14	"errors"
15	"net/http"
16	"strconv"
17	"strings"
18
19	"github.com/PuerkitoBio/goquery"
20	"github.com/google/go-github/v28/github"
21)
22
23// AppRestrictionsEnabled returns whether the specified organization has
24// restricted third-party application access.
25func (c *Client) AppRestrictionsEnabled(org string) (bool, error) {
26	doc, err := c.get("/organizations/%s/settings/oauth_application_policy", org)
27	if err != nil {
28		return false, err
29	}
30
31	s := doc.Find(".oauth-application-whitelist svg").First()
32	if s.Length() == 0 {
33		return false, errors.New("unable to find expected markup")
34	}
35
36	if s.HasClass("octicon-check") {
37		return true, nil
38	}
39	if s.HasClass("octicon-alert") {
40		return false, nil
41	}
42
43	return false, errors.New("unable to find expected markup")
44}
45
46// ListOAuthApps lists the reviewed OAuth Applications for the
47// specified organization (whether approved or denied).
48func (c *Client) ListOAuthApps(org string) ([]OAuthApp, error) {
49	doc, err := c.get("/organizations/%s/settings/oauth_application_policy", org)
50	if err != nil {
51		return nil, err
52	}
53
54	var apps []OAuthApp
55	doc.Find(".oauth-application-whitelist ul > li").Each(func(i int, s *goquery.Selection) {
56		var app OAuthApp
57		app.Name = s.Find(".request-info strong").First().Text()
58		app.Description = s.Find(".request-info .application-description").Text()
59
60		if editURL, ok := s.Find(".request-indicator a.edit-link").Attr("href"); ok {
61			app.ID = intFromLastPathSegment(editURL)
62		}
63
64		if r := s.Find(".request-indicator .requestor"); r.Length() > 0 {
65			app.State = OAuthAppRequested
66			app.RequestedBy = r.Text()
67			if editURL, ok := s.Find(".request-indicator a").Last().Attr("href"); ok {
68				app.ID = intFromLastPathSegment(editURL)
69			}
70		} else if r := s.Find(".request-indicator .approved-request"); r.Length() > 0 {
71			app.State = OAuthAppApproved
72		} else if r := s.Find(".request-indicator .denied-request"); r.Length() > 0 {
73			app.State = OAuthAppDenied
74		}
75		apps = append(apps, app)
76	})
77
78	return apps, nil
79}
80
81func intFromLastPathSegment(s string) int {
82	seg := strings.Split(s, "/")
83	if len(seg) > 0 {
84		i, _ := strconv.Atoi(seg[len(seg)-1])
85		return i
86	}
87	return 0
88}
89
90// OAuthAppReviewState indicates the current state of a requested OAuth Application.
91type OAuthAppReviewState int
92
93const (
94	// OAuthAppRequested indicates access has been requested, but not reviewed
95	OAuthAppRequested OAuthAppReviewState = iota + 1
96	// OAuthAppApproved indicates access has been approved
97	OAuthAppApproved
98	// OAuthAppDenied indicates access has been denied
99	OAuthAppDenied
100)
101
102// OAuthApp represents an OAuth application that has been reviewed for access to organization data.
103type OAuthApp struct {
104	ID          int
105	Name        string
106	Description string
107	State       OAuthAppReviewState
108	RequestedBy string
109}
110
111// AppManifest represents a GitHub App manifest, used for preconfiguring
112// GitHub App configuration.
113type AppManifest struct {
114	// The name of the GitHub App.
115	Name *string `json:"name,omitempty"`
116	//Required. The homepage of your GitHub App.
117	URL *string `json:"url,omitempty"`
118	// Required. The configuration of the GitHub App's webhook.
119	HookAttributes map[string]string `json:"hook_attributes,omitempty"`
120	// The full URL to redirect to after the person installs the GitHub App.
121	RedirectURL *string `json:"redirect_url,omitempty"`
122	// A description of the GitHub App.
123	Description *string `json:"description,omitempty"`
124	// Set to true when your GitHub App is available to the public or false when
125	// it is only accessible to the owner of the app.
126	Public *bool `json:"public,omitempty"`
127	// The list of events the GitHub App subscribes to.
128	DefaultEvents []string `json:"default_events,omitempty"`
129	// The set of permissions needed by the GitHub App.
130	DefaultPermissions *github.InstallationPermissions `json:"default_permissions,omitempty"`
131}
132
133// CreateApp creates a new GitHub App with the given manifest configuration.
134func (c *Client) CreateApp(m *AppManifest) (*http.Response, error) {
135	u, err := c.baseURL.Parse("/settings/apps/new")
136	if err != nil {
137		return nil, err
138	}
139
140	body, err := json.Marshal(map[string]*AppManifest{"manifest": m})
141	if err != nil {
142		return nil, err
143	}
144
145	return c.Client.Post(u.String(), "json", bytes.NewReader(body))
146}
147