1package github
2
3import (
4	"context"
5	"fmt"
6	"net/url"
7	"strings"
8	"time"
9
10	"github.com/hashicorp/errwrap"
11	"github.com/hashicorp/vault/sdk/framework"
12	"github.com/hashicorp/vault/sdk/helper/tokenutil"
13	"github.com/hashicorp/vault/sdk/logical"
14)
15
16func pathConfig(b *backend) *framework.Path {
17	p := &framework.Path{
18		Pattern: "config",
19		Fields: map[string]*framework.FieldSchema{
20			"organization": &framework.FieldSchema{
21				Type:        framework.TypeString,
22				Description: "The organization users must be part of",
23			},
24
25			"base_url": &framework.FieldSchema{
26				Type: framework.TypeString,
27				Description: `The API endpoint to use. Useful if you
28are running GitHub Enterprise or an
29API-compatible authentication server.`,
30				DisplayAttrs: &framework.DisplayAttributes{
31					Name:  "Base URL",
32					Group: "GitHub Options",
33				},
34			},
35			"ttl": &framework.FieldSchema{
36				Type:        framework.TypeDurationSecond,
37				Description: tokenutil.DeprecationText("token_ttl"),
38				Deprecated:  true,
39			},
40			"max_ttl": &framework.FieldSchema{
41				Type:        framework.TypeDurationSecond,
42				Description: tokenutil.DeprecationText("token_max_ttl"),
43				Deprecated:  true,
44			},
45		},
46
47		Callbacks: map[logical.Operation]framework.OperationFunc{
48			logical.UpdateOperation: b.pathConfigWrite,
49			logical.ReadOperation:   b.pathConfigRead,
50		},
51	}
52
53	tokenutil.AddTokenFields(p.Fields)
54	p.Fields["token_policies"].Description += ". This will apply to all tokens generated by this auth method, in addition to any policies configured for specific users/groups."
55	return p
56}
57
58func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
59	c, err := b.Config(ctx, req.Storage)
60	if err != nil {
61		return nil, err
62	}
63	if c == nil {
64		c = &config{}
65	}
66
67	if organizationRaw, ok := data.GetOk("organization"); ok {
68		c.Organization = organizationRaw.(string)
69	}
70
71	if baseURLRaw, ok := data.GetOk("base_url"); ok {
72		baseURL := baseURLRaw.(string)
73		_, err := url.Parse(baseURL)
74		if err != nil {
75			return logical.ErrorResponse(fmt.Sprintf("Error parsing given base_url: %s", err)), nil
76		}
77		if !strings.HasSuffix(baseURL, "/") {
78			baseURL += "/"
79		}
80		c.BaseURL = baseURL
81	}
82
83	if err := c.ParseTokenFields(req, data); err != nil {
84		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
85	}
86
87	// Handle upgrade cases
88	{
89		if err := tokenutil.UpgradeValue(data, "ttl", "token_ttl", &c.TTL, &c.TokenTTL); err != nil {
90			return logical.ErrorResponse(err.Error()), nil
91		}
92
93		if err := tokenutil.UpgradeValue(data, "max_ttl", "token_max_ttl", &c.MaxTTL, &c.TokenMaxTTL); err != nil {
94			return logical.ErrorResponse(err.Error()), nil
95		}
96	}
97
98	entry, err := logical.StorageEntryJSON("config", c)
99	if err != nil {
100		return nil, err
101	}
102
103	if err := req.Storage.Put(ctx, entry); err != nil {
104		return nil, err
105	}
106
107	return nil, nil
108}
109
110func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
111	config, err := b.Config(ctx, req.Storage)
112	if err != nil {
113		return nil, err
114	}
115	if config == nil {
116		return nil, nil
117	}
118
119	d := map[string]interface{}{
120		"organization": config.Organization,
121		"base_url":     config.BaseURL,
122	}
123	config.PopulateTokenData(d)
124
125	if config.TTL > 0 {
126		d["ttl"] = int64(config.TTL.Seconds())
127	}
128	if config.MaxTTL > 0 {
129		d["max_ttl"] = int64(config.MaxTTL.Seconds())
130	}
131
132	return &logical.Response{
133		Data: d,
134	}, nil
135}
136
137// Config returns the configuration for this backend.
138func (b *backend) Config(ctx context.Context, s logical.Storage) (*config, error) {
139	entry, err := s.Get(ctx, "config")
140	if err != nil {
141		return nil, err
142	}
143	if entry == nil {
144		return nil, nil
145	}
146
147	var result config
148	if entry != nil {
149		if err := entry.DecodeJSON(&result); err != nil {
150			return nil, errwrap.Wrapf("error reading configuration: {{err}}", err)
151		}
152	}
153
154	if result.TokenTTL == 0 && result.TTL > 0 {
155		result.TokenTTL = result.TTL
156	}
157	if result.TokenMaxTTL == 0 && result.MaxTTL > 0 {
158		result.TokenMaxTTL = result.MaxTTL
159	}
160
161	return &result, nil
162}
163
164type config struct {
165	tokenutil.TokenParams
166
167	Organization string        `json:"organization" structs:"organization" mapstructure:"organization"`
168	BaseURL      string        `json:"base_url" structs:"base_url" mapstructure:"base_url"`
169	TTL          time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"`
170	MaxTTL       time.Duration `json:"max_ttl" structs:"max_ttl" mapstructure:"max_ttl"`
171}
172