1package deb
2
3import (
4	"encoding/json"
5	"fmt"
6	"os"
7
8	"github.com/DisposaBoy/JsonConfigReader"
9	"github.com/aptly-dev/aptly/pgp"
10	"github.com/aptly-dev/aptly/utils"
11)
12
13// UploadersRule is single rule of format: what packages can group or key upload
14type UploadersRule struct {
15	Condition         string       `json:"condition"`
16	Allow             []string     `json:"allow"`
17	Deny              []string     `json:"deny"`
18	CompiledCondition PackageQuery `json:"-" codec:"-"`
19}
20
21func (u UploadersRule) String() string {
22	b, _ := json.Marshal(u)
23	return string(b)
24}
25
26// Uploaders is configuration of restrictions for .changes file importing
27type Uploaders struct {
28	Groups map[string][]string `json:"groups"`
29	Rules  []UploadersRule     `json:"rules"`
30}
31
32func (u *Uploaders) String() string {
33	b, _ := json.Marshal(u)
34	return string(b)
35}
36
37// NewUploadersFromFile loads Uploaders structue from .json file
38func NewUploadersFromFile(path string) (*Uploaders, error) {
39	uploaders := &Uploaders{}
40	f, err := os.Open(path)
41	if err != nil {
42		return nil, fmt.Errorf("error loading uploaders file: %s", err)
43	}
44	defer f.Close()
45
46	err = json.NewDecoder(JsonConfigReader.New(f)).Decode(&uploaders)
47	if err != nil {
48		return nil, fmt.Errorf("error loading uploaders file: %s", err)
49	}
50
51	return uploaders, nil
52}
53
54func (u *Uploaders) expandGroupsInternal(items []string, trail []string) []string {
55	result := []string{}
56
57	for _, item := range items {
58		// stop infinite recursion
59		if utils.StrSliceHasItem(trail, item) {
60			continue
61		}
62
63		group, ok := u.Groups[item]
64		if !ok {
65			result = append(result, item)
66		} else {
67			newTrail := append([]string(nil), trail...)
68			result = append(result, u.expandGroupsInternal(group, append(newTrail, item))...)
69		}
70	}
71
72	return result
73}
74
75// ExpandGroups expands list of keys/groups into list of keys
76func (u *Uploaders) ExpandGroups(items []string) []string {
77	result := u.expandGroupsInternal(items, []string{})
78
79	return utils.StrSliceDeduplicate(result)
80}
81
82// IsAllowed checks whether listed keys are allowed to upload given .changes file
83func (u *Uploaders) IsAllowed(changes *Changes) error {
84	for _, rule := range u.Rules {
85		if rule.CompiledCondition.Matches(changes) {
86			deny := u.ExpandGroups(rule.Deny)
87			for _, key := range changes.SignatureKeys {
88				for _, item := range deny {
89					if item == "*" || key.Matches(pgp.Key(item)) {
90						return fmt.Errorf("denied according to rule: %s", rule)
91					}
92				}
93			}
94
95			allow := u.ExpandGroups(rule.Allow)
96			for _, key := range changes.SignatureKeys {
97				for _, item := range allow {
98					if item == "*" || key.Matches(pgp.Key(item)) {
99						return nil
100					}
101				}
102			}
103		}
104	}
105
106	return fmt.Errorf("denied as no rule matches")
107}
108