1// Copyright (c) 2015-2021 MinIO, Inc.
2//
3// This file is part of MinIO Object Storage stack
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18package iampolicy
19
20import (
21	"encoding/json"
22	"net"
23	"reflect"
24	"strings"
25	"testing"
26	"time"
27
28	"github.com/minio/minio-go/v7/pkg/set"
29	"github.com/minio/pkg/bucket/policy"
30	"github.com/minio/pkg/bucket/policy/condition"
31)
32
33func TestGetPoliciesFromClaims(t *testing.T) {
34	attributesArray := `{
35  "exp": 1594690452,
36  "iat": 1594689552,
37  "auth_time": 1594689552,
38  "jti": "18ed05c9-2c69-45d5-a33f-8c94aca99ad5",
39  "iss": "http://localhost:8080/auth/realms/minio",
40  "aud": "account",
41  "sub": "7e5e2f30-1c97-4616-8623-2eae14dee9b1",
42  "typ": "ID",
43  "azp": "account",
44  "nonce": "66ZoLzwJbjdkiedI",
45  "session_state": "3df7b526-5310-4038-9f35-50ecd295a31d",
46  "acr": "1",
47  "upn": "harsha",
48  "address": {},
49  "email_verified": false,
50  "groups": [
51    "offline_access"
52  ],
53  "preferred_username": "harsha",
54  "policy": [
55    "readwrite",
56    "readwrite,readonly",
57    "  readonly",
58    ""
59  ]}`
60	var m = make(map[string]interface{})
61	if err := json.Unmarshal([]byte(attributesArray), &m); err != nil {
62		t.Fatal(err)
63	}
64	var expectedSet = set.CreateStringSet("readwrite", "readonly")
65	gotSet, ok := GetPoliciesFromClaims(m, "policy")
66	if !ok {
67		t.Fatal("no policy claim was found")
68	}
69	if gotSet.IsEmpty() {
70		t.Fatal("no policies were found in policy claim")
71	}
72	if !gotSet.Equals(expectedSet) {
73		t.Fatalf("Expected %v got %v", expectedSet, gotSet)
74	}
75}
76
77func TestPolicyIsAllowed(t *testing.T) {
78	case1Policy := Policy{
79		Version: DefaultVersion,
80		Statements: []Statement{
81			NewStatement(
82				policy.Allow,
83				NewActionSet(GetBucketLocationAction, PutObjectAction),
84				NewResourceSet(NewResource("*", "")),
85				condition.NewFunctions(),
86			)},
87	}
88
89	case2Policy := Policy{
90		Version: DefaultVersion,
91		Statements: []Statement{
92			NewStatement(
93				policy.Allow,
94				NewActionSet(GetObjectAction, PutObjectAction),
95				NewResourceSet(NewResource("mybucket", "/myobject*")),
96				condition.NewFunctions(),
97			)},
98	}
99
100	_, IPNet, err := net.ParseCIDR("192.168.1.0/24")
101	if err != nil {
102		t.Fatalf("unexpected error. %v\n", err)
103	}
104	func1, err := condition.NewIPAddressFunc(
105		condition.AWSSourceIP.ToKey(),
106		IPNet,
107	)
108	if err != nil {
109		t.Fatalf("unexpected error. %v\n", err)
110	}
111
112	case3Policy := Policy{
113		Version: DefaultVersion,
114		Statements: []Statement{
115			NewStatement(
116				policy.Allow,
117				NewActionSet(GetObjectAction, PutObjectAction),
118				NewResourceSet(NewResource("mybucket", "/myobject*")),
119				condition.NewFunctions(func1),
120			)},
121	}
122
123	case4Policy := Policy{
124		Version: DefaultVersion,
125		Statements: []Statement{
126			NewStatement(
127				policy.Deny,
128				NewActionSet(GetObjectAction, PutObjectAction),
129				NewResourceSet(NewResource("mybucket", "/myobject*")),
130				condition.NewFunctions(func1),
131			)},
132	}
133
134	anonGetBucketLocationArgs := Args{
135		AccountName:     "Q3AM3UQ867SPQQA43P2F",
136		Action:          GetBucketLocationAction,
137		BucketName:      "mybucket",
138		ConditionValues: map[string][]string{},
139	}
140
141	anonPutObjectActionArgs := Args{
142		AccountName: "Q3AM3UQ867SPQQA43P2F",
143		Action:      PutObjectAction,
144		BucketName:  "mybucket",
145		ConditionValues: map[string][]string{
146			"x-amz-copy-source": {"mybucket/myobject"},
147			"SourceIp":          {"192.168.1.10"},
148		},
149		ObjectName: "myobject",
150	}
151
152	anonGetObjectActionArgs := Args{
153		AccountName:     "Q3AM3UQ867SPQQA43P2F",
154		Action:          GetObjectAction,
155		BucketName:      "mybucket",
156		ConditionValues: map[string][]string{},
157		ObjectName:      "myobject",
158	}
159
160	getBucketLocationArgs := Args{
161		AccountName:     "Q3AM3UQ867SPQQA43P2F",
162		Action:          GetBucketLocationAction,
163		BucketName:      "mybucket",
164		ConditionValues: map[string][]string{},
165	}
166
167	putObjectActionArgs := Args{
168		AccountName: "Q3AM3UQ867SPQQA43P2F",
169		Action:      PutObjectAction,
170		BucketName:  "mybucket",
171		ConditionValues: map[string][]string{
172			"x-amz-copy-source": {"mybucket/myobject"},
173			"SourceIp":          {"192.168.1.10"},
174		},
175		ObjectName: "myobject",
176	}
177
178	getObjectActionArgs := Args{
179		AccountName:     "Q3AM3UQ867SPQQA43P2F",
180		Action:          GetObjectAction,
181		BucketName:      "mybucket",
182		ConditionValues: map[string][]string{},
183		ObjectName:      "myobject",
184	}
185
186	testCases := []struct {
187		policy         Policy
188		args           Args
189		expectedResult bool
190	}{
191		{case1Policy, anonGetBucketLocationArgs, true},
192		{case1Policy, anonPutObjectActionArgs, true},
193		{case1Policy, anonGetObjectActionArgs, false},
194		{case1Policy, getBucketLocationArgs, true},
195		{case1Policy, putObjectActionArgs, true},
196		{case1Policy, getObjectActionArgs, false},
197
198		{case2Policy, anonGetBucketLocationArgs, false},
199		{case2Policy, anonPutObjectActionArgs, true},
200		{case2Policy, anonGetObjectActionArgs, true},
201		{case2Policy, getBucketLocationArgs, false},
202		{case2Policy, putObjectActionArgs, true},
203		{case2Policy, getObjectActionArgs, true},
204
205		{case3Policy, anonGetBucketLocationArgs, false},
206		{case3Policy, anonPutObjectActionArgs, true},
207		{case3Policy, anonGetObjectActionArgs, false},
208		{case3Policy, getBucketLocationArgs, false},
209		{case3Policy, putObjectActionArgs, true},
210		{case3Policy, getObjectActionArgs, false},
211
212		{case4Policy, anonGetBucketLocationArgs, false},
213		{case4Policy, anonPutObjectActionArgs, false},
214		{case4Policy, anonGetObjectActionArgs, false},
215		{case4Policy, getBucketLocationArgs, false},
216		{case4Policy, putObjectActionArgs, false},
217		{case4Policy, getObjectActionArgs, false},
218	}
219
220	for i, testCase := range testCases {
221		result := testCase.policy.IsAllowed(testCase.args)
222
223		if result != testCase.expectedResult {
224			t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
225		}
226	}
227}
228
229func TestPolicyIsEmpty(t *testing.T) {
230	case1Policy := Policy{
231		Version: DefaultVersion,
232		Statements: []Statement{
233			NewStatement(
234				policy.Allow,
235				NewActionSet(PutObjectAction),
236				NewResourceSet(NewResource("mybucket", "/myobject*")),
237				condition.NewFunctions(),
238			),
239		},
240	}
241
242	case2Policy := Policy{
243		ID:      "MyPolicyForMyBucket",
244		Version: DefaultVersion,
245	}
246
247	testCases := []struct {
248		policy         Policy
249		expectedResult bool
250	}{
251		{case1Policy, false},
252		{case2Policy, true},
253	}
254
255	for i, testCase := range testCases {
256		result := testCase.policy.IsEmpty()
257
258		if result != testCase.expectedResult {
259			t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
260		}
261	}
262}
263
264func TestPolicyIsValid(t *testing.T) {
265	case1Policy := Policy{
266		Version: DefaultVersion,
267		Statements: []Statement{
268			NewStatement(
269				policy.Allow,
270				NewActionSet(PutObjectAction),
271				NewResourceSet(NewResource("mybucket", "/myobject*")),
272				condition.NewFunctions(),
273			),
274		},
275	}
276
277	case2Policy := Policy{
278		Version: DefaultVersion,
279		Statements: []Statement{
280			NewStatement(
281				policy.Allow,
282				NewActionSet(PutObjectAction),
283				NewResourceSet(NewResource("mybucket", "/myobject*")),
284				condition.NewFunctions(),
285			),
286			NewStatement(
287				policy.Deny,
288				NewActionSet(GetObjectAction),
289				NewResourceSet(NewResource("mybucket", "/myobject*")),
290				condition.NewFunctions(),
291			),
292		},
293	}
294
295	case3Policy := Policy{
296		Version: DefaultVersion,
297		Statements: []Statement{
298			NewStatement(
299				policy.Allow,
300				NewActionSet(PutObjectAction),
301				NewResourceSet(NewResource("mybucket", "/myobject*")),
302				condition.NewFunctions(),
303			),
304			NewStatement(
305				policy.Deny,
306				NewActionSet(PutObjectAction),
307				NewResourceSet(NewResource("mybucket", "/yourobject*")),
308				condition.NewFunctions(),
309			),
310		},
311	}
312
313	func1, err := condition.NewNullFunc(
314		condition.S3XAmzCopySource.ToKey(),
315		true,
316	)
317	if err != nil {
318		t.Fatalf("unexpected error. %v\n", err)
319	}
320	func2, err := condition.NewNullFunc(
321		condition.S3XAmzServerSideEncryption.ToKey(),
322		false,
323	)
324	if err != nil {
325		t.Fatalf("unexpected error. %v\n", err)
326	}
327
328	case4Policy := Policy{
329		Version: DefaultVersion,
330		Statements: []Statement{
331			NewStatement(
332				policy.Allow,
333				NewActionSet(PutObjectAction),
334				NewResourceSet(NewResource("mybucket", "/myobject*")),
335				condition.NewFunctions(func1),
336			),
337			NewStatement(
338				policy.Deny,
339				NewActionSet(PutObjectAction),
340				NewResourceSet(NewResource("mybucket", "/myobject*")),
341				condition.NewFunctions(func2),
342			),
343		},
344	}
345
346	case5Policy := Policy{
347		Version: "17-10-2012",
348		Statements: []Statement{
349			NewStatement(
350				policy.Allow,
351				NewActionSet(PutObjectAction),
352				NewResourceSet(NewResource("mybucket", "/myobject*")),
353				condition.NewFunctions(),
354			),
355		},
356	}
357
358	case6Policy := Policy{
359		ID:      "MyPolicyForMyBucket1",
360		Version: DefaultVersion,
361		Statements: []Statement{
362			NewStatement(
363				policy.Allow,
364				NewActionSet(GetObjectAction, PutObjectAction),
365				NewResourceSet(NewResource("mybucket", "myobject*")),
366				condition.NewFunctions(func1, func2),
367			),
368		},
369	}
370
371	case7Policy := Policy{
372		Version: DefaultVersion,
373		Statements: []Statement{
374			NewStatement(
375				policy.Allow,
376				NewActionSet(PutObjectAction),
377				NewResourceSet(NewResource("mybucket", "/myobject*")),
378				condition.NewFunctions(),
379			),
380			NewStatement(
381				policy.Deny,
382				NewActionSet(PutObjectAction),
383				NewResourceSet(NewResource("mybucket", "/myobject*")),
384				condition.NewFunctions(),
385			),
386		},
387	}
388
389	case8Policy := Policy{
390		Version: DefaultVersion,
391		Statements: []Statement{
392			NewStatement(
393				policy.Allow,
394				NewActionSet(PutObjectAction),
395				NewResourceSet(NewResource("mybucket", "/myobject*")),
396				condition.NewFunctions(),
397			),
398			NewStatement(
399				policy.Allow,
400				NewActionSet(PutObjectAction),
401				NewResourceSet(NewResource("mybucket", "/myobject*")),
402				condition.NewFunctions(),
403			),
404		},
405	}
406
407	testCases := []struct {
408		policy    Policy
409		expectErr bool
410	}{
411		{case1Policy, false},
412		// allowed duplicate principal.
413		{case2Policy, false},
414		// allowed duplicate principal and action.
415		{case3Policy, false},
416		// allowed duplicate principal, action and resource.
417		{case4Policy, false},
418		// Invalid version error.
419		{case5Policy, true},
420		// Invalid statement error.
421		{case6Policy, true},
422		// Duplicate statement different Effects.
423		{case7Policy, false},
424		// Duplicate statement same Effects, duplicate effect will be removed.
425		{case8Policy, false},
426	}
427
428	for i, testCase := range testCases {
429		err := testCase.policy.isValid()
430		expectErr := (err != nil)
431
432		if expectErr != testCase.expectErr {
433			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
434		}
435	}
436}
437
438// Parse config with location constraints
439func TestPolicyParseConfig(t *testing.T) {
440	policy1LocationConstraint := `{
441   "Version":"2012-10-17",
442   "Statement":[
443      {
444         "Sid":"statement1",
445         "Effect":"Allow",
446         "Action": "s3:CreateBucket",
447         "Resource": "arn:aws:s3:::*",
448         "Condition": {
449             "StringLike": {
450                 "s3:LocationConstraint": "us-east-1"
451             }
452         }
453       },
454      {
455         "Sid":"statement2",
456         "Effect":"Deny",
457         "Action": "s3:CreateBucket",
458         "Resource": "arn:aws:s3:::*",
459         "Condition": {
460             "StringNotLike": {
461                 "s3:LocationConstraint": "us-east-1"
462             }
463         }
464       }
465    ]
466}`
467	policy2Condition := `{
468    "Version": "2012-10-17",
469    "Statement": [
470        {
471            "Sid": "statement1",
472            "Effect": "Allow",
473            "Action": "s3:GetObjectVersion",
474            "Resource": "arn:aws:s3:::test/HappyFace.jpg"
475        },
476        {
477            "Sid": "statement2",
478            "Effect": "Deny",
479            "Action": "s3:GetObjectVersion",
480            "Resource": "arn:aws:s3:::test/HappyFace.jpg",
481            "Condition": {
482                "StringNotEquals": {
483                    "s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
484                }
485            }
486        }
487    ]
488}`
489
490	policy3ConditionActionRegex := `{
491    "Version": "2012-10-17",
492    "Statement": [
493        {
494            "Sid": "statement2",
495            "Effect": "Allow",
496            "Action": "s3:Get*",
497            "Resource": "arn:aws:s3:::test/HappyFace.jpg",
498            "Condition": {
499                "StringEquals": {
500                    "s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
501                }
502            }
503        }
504    ]
505}`
506
507	policy4ConditionAction := `{
508    "Version": "2012-10-17",
509    "Statement": [
510        {
511            "Sid": "statement2",
512            "Effect": "Allow",
513            "Action": "s3:GetObject",
514            "Resource": "arn:aws:s3:::test/HappyFace.jpg",
515            "Condition": {
516                "StringEquals": {
517                    "s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
518                }
519            }
520        }
521    ]
522}`
523
524	policy5ConditionCurrenTime := `{
525 "Version": "2012-10-17",
526 "Statement": [
527  {
528   "Effect": "Allow",
529   "Action": [
530    "s3:Get*",
531    "s3:Put*"
532   ],
533   "Resource": [
534    "arn:aws:s3:::test/*"
535   ],
536   "Condition": {
537    "DateGreaterThan": {
538     "aws:CurrentTime": [
539      "2017-02-28T00:00:00Z"
540     ]
541    }
542   }
543  }
544 ]
545}`
546
547	policy5ConditionCurrenTimeLesser := `{
548 "Version": "2012-10-17",
549 "Statement": [
550  {
551   "Effect": "Allow",
552   "Action": [
553    "s3:Get*",
554    "s3:Put*"
555   ],
556   "Resource": [
557    "arn:aws:s3:::test/*"
558   ],
559   "Condition": {
560    "DateLessThan": {
561     "aws:CurrentTime": [
562      "2017-02-28T00:00:00Z"
563     ]
564    }
565   }
566  }
567 ]
568}`
569
570	tests := []struct {
571		p       string
572		args    Args
573		allowed bool
574	}{
575		{
576			p:       policy1LocationConstraint,
577			allowed: true,
578			args: Args{
579				AccountName:     "allowed",
580				Action:          CreateBucketAction,
581				BucketName:      "test",
582				ConditionValues: map[string][]string{"LocationConstraint": {"us-east-1"}},
583			},
584		},
585		{
586			p:       policy1LocationConstraint,
587			allowed: false,
588			args: Args{
589				AccountName:     "disallowed",
590				Action:          CreateBucketAction,
591				BucketName:      "test",
592				ConditionValues: map[string][]string{"LocationConstraint": {"us-east-2"}},
593			},
594		},
595		{
596			p:       policy2Condition,
597			allowed: true,
598			args: Args{
599				AccountName:     "allowed",
600				Action:          GetObjectAction,
601				BucketName:      "test",
602				ObjectName:      "HappyFace.jpg",
603				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
604			},
605		},
606		{
607			p:       policy2Condition,
608			allowed: false,
609			args: Args{
610				AccountName:     "disallowed",
611				Action:          GetObjectAction,
612				BucketName:      "test",
613				ObjectName:      "HappyFace.jpg",
614				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5f"}},
615			},
616		},
617		{
618			p:       policy3ConditionActionRegex,
619			allowed: true,
620			args: Args{
621				AccountName:     "allowed",
622				Action:          GetObjectAction,
623				BucketName:      "test",
624				ObjectName:      "HappyFace.jpg",
625				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
626			},
627		},
628		{
629			p:       policy3ConditionActionRegex,
630			allowed: false,
631			args: Args{
632				AccountName:     "disallowed",
633				Action:          GetObjectAction,
634				BucketName:      "test",
635				ObjectName:      "HappyFace.jpg",
636				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5f"}},
637			},
638		},
639		{
640			p:       policy4ConditionAction,
641			allowed: true,
642			args: Args{
643				AccountName:     "allowed",
644				Action:          GetObjectAction,
645				BucketName:      "test",
646				ObjectName:      "HappyFace.jpg",
647				ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
648			},
649		},
650		{
651			p:       policy5ConditionCurrenTime,
652			allowed: true,
653			args: Args{
654				AccountName: "allowed",
655				Action:      GetObjectAction,
656				BucketName:  "test",
657				ObjectName:  "HappyFace.jpg",
658				ConditionValues: map[string][]string{
659					"CurrentTime": {time.Now().Format(time.RFC3339)},
660				},
661			},
662		},
663		{
664			p:       policy5ConditionCurrenTimeLesser,
665			allowed: false,
666			args: Args{
667				AccountName: "disallowed",
668				Action:      GetObjectAction,
669				BucketName:  "test",
670				ObjectName:  "HappyFace.jpg",
671				ConditionValues: map[string][]string{
672					"CurrentTime": {time.Now().Format(time.RFC3339)},
673				},
674			},
675		},
676	}
677	for _, test := range tests {
678		test := test
679		t.Run(test.args.AccountName, func(t *testing.T) {
680			ip, err := ParseConfig(strings.NewReader(test.p))
681			if err != nil {
682				t.Error(err)
683			}
684			if got := ip.IsAllowed(test.args); got != test.allowed {
685				t.Errorf("Expected %t, got %t", test.allowed, got)
686			}
687		})
688	}
689}
690
691func TestPolicyUnmarshalJSONAndValidate(t *testing.T) {
692	case1Data := []byte(`{
693    "ID": "MyPolicyForMyBucket1",
694    "Version": "2012-10-17",
695    "Statement": [
696        {
697            "Sid": "SomeId1",
698            "Effect": "Allow",
699            "Action": "s3:PutObject",
700            "Resource": "arn:aws:s3:::mybucket/myobject*"
701        }
702    ]
703}`)
704	case1Policy := Policy{
705		ID:      "MyPolicyForMyBucket1",
706		Version: DefaultVersion,
707		Statements: []Statement{
708			NewStatement(
709				policy.Allow,
710				NewActionSet(PutObjectAction),
711				NewResourceSet(NewResource("mybucket", "/myobject*")),
712				condition.NewFunctions(),
713			),
714		},
715	}
716	case1Policy.Statements[0].SID = "SomeId1"
717
718	case2Data := []byte(`{
719    "Version": "2012-10-17",
720    "Statement": [
721        {
722            "Effect": "Allow",
723            "Action": "s3:PutObject",
724            "Resource": "arn:aws:s3:::mybucket/myobject*"
725        },
726        {
727            "Effect": "Deny",
728            "Action": "s3:GetObject",
729            "Resource": "arn:aws:s3:::mybucket/yourobject*",
730            "Condition": {
731                "IpAddress": {
732                    "aws:SourceIp": "192.168.1.0/24"
733                }
734            }
735        }
736    ]
737}`)
738	_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
739	if err != nil {
740		t.Fatalf("unexpected error. %v\n", err)
741	}
742	func1, err := condition.NewIPAddressFunc(
743		condition.AWSSourceIP.ToKey(),
744		IPNet1,
745	)
746	if err != nil {
747		t.Fatalf("unexpected error. %v\n", err)
748	}
749
750	case2Policy := Policy{
751		Version: DefaultVersion,
752		Statements: []Statement{
753			NewStatement(
754				policy.Allow,
755				NewActionSet(PutObjectAction),
756				NewResourceSet(NewResource("mybucket", "/myobject*")),
757				condition.NewFunctions(),
758			),
759			NewStatement(
760				policy.Deny,
761				NewActionSet(GetObjectAction),
762				NewResourceSet(NewResource("mybucket", "/yourobject*")),
763				condition.NewFunctions(func1),
764			),
765		},
766	}
767
768	case3Data := []byte(`{
769    "ID": "MyPolicyForMyBucket1",
770    "Version": "2012-10-17",
771    "Statement": [
772        {
773            "Effect": "Allow",
774            "Action": "s3:GetObject",
775            "Resource": "arn:aws:s3:::mybucket/myobject*"
776        },
777        {
778            "Effect": "Allow",
779            "Action": "s3:PutObject",
780            "Resource": "arn:aws:s3:::mybucket/myobject*"
781        }
782    ]
783}`)
784	case3Policy := Policy{
785		ID:      "MyPolicyForMyBucket1",
786		Version: DefaultVersion,
787		Statements: []Statement{
788			NewStatement(
789				policy.Allow,
790				NewActionSet(GetObjectAction),
791				NewResourceSet(NewResource("mybucket", "/myobject*")),
792				condition.NewFunctions(),
793			),
794			NewStatement(
795				policy.Allow,
796				NewActionSet(PutObjectAction),
797				NewResourceSet(NewResource("mybucket", "/myobject*")),
798				condition.NewFunctions(),
799			),
800		},
801	}
802
803	case4Data := []byte(`{
804    "ID": "MyPolicyForMyBucket1",
805    "Version": "2012-10-17",
806    "Statement": [
807        {
808            "Effect": "Allow",
809            "Action": "s3:PutObject",
810            "Resource": "arn:aws:s3:::mybucket/myobject*"
811        },
812        {
813            "Effect": "Allow",
814            "Action": "s3:GetObject",
815            "Resource": "arn:aws:s3:::mybucket/myobject*"
816        }
817    ]
818}`)
819	case4Policy := Policy{
820		ID:      "MyPolicyForMyBucket1",
821		Version: DefaultVersion,
822		Statements: []Statement{
823			NewStatement(
824				policy.Allow,
825				NewActionSet(PutObjectAction),
826				NewResourceSet(NewResource("mybucket", "/myobject*")),
827				condition.NewFunctions(),
828			),
829			NewStatement(
830				policy.Allow,
831				NewActionSet(GetObjectAction),
832				NewResourceSet(NewResource("mybucket", "/myobject*")),
833				condition.NewFunctions(),
834			),
835		},
836	}
837
838	case5Data := []byte(`{
839    "ID": "MyPolicyForMyBucket1",
840    "Version": "2012-10-17",
841    "Statement": [
842        {
843            "Effect": "Allow",
844            "Action": "s3:PutObject",
845            "Resource": "arn:aws:s3:::mybucket/myobject*"
846        },
847        {
848            "Effect": "Allow",
849            "Action": "s3:PutObject",
850            "Resource": "arn:aws:s3:::mybucket/yourobject*"
851        }
852    ]
853}`)
854	case5Policy := Policy{
855		ID:      "MyPolicyForMyBucket1",
856		Version: DefaultVersion,
857		Statements: []Statement{
858			NewStatement(
859				policy.Allow,
860				NewActionSet(PutObjectAction),
861				NewResourceSet(NewResource("mybucket", "/myobject*")),
862				condition.NewFunctions(),
863			),
864			NewStatement(
865				policy.Allow,
866				NewActionSet(PutObjectAction),
867				NewResourceSet(NewResource("mybucket", "/yourobject*")),
868				condition.NewFunctions(),
869			),
870		},
871	}
872
873	case6Data := []byte(`{
874    "ID": "MyPolicyForMyBucket1",
875    "Version": "2012-10-17",
876    "Statement": [
877        {
878            "Effect": "Allow",
879            "Action": "s3:PutObject",
880            "Resource": "arn:aws:s3:::mybucket/myobject*",
881            "Condition": {
882                "IpAddress": {
883                    "aws:SourceIp": "192.168.1.0/24"
884                }
885            }
886        },
887        {
888            "Effect": "Allow",
889            "Action": "s3:PutObject",
890            "Resource": "arn:aws:s3:::mybucket/myobject*",
891            "Condition": {
892                "IpAddress": {
893                    "aws:SourceIp": "192.168.2.0/24"
894                }
895            }
896        }
897    ]
898}`)
899	_, IPNet2, err := net.ParseCIDR("192.168.2.0/24")
900	if err != nil {
901		t.Fatalf("unexpected error. %v\n", err)
902	}
903	func2, err := condition.NewIPAddressFunc(
904		condition.AWSSourceIP.ToKey(),
905		IPNet2,
906	)
907	if err != nil {
908		t.Fatalf("unexpected error. %v\n", err)
909	}
910
911	case6Policy := Policy{
912		ID:      "MyPolicyForMyBucket1",
913		Version: DefaultVersion,
914		Statements: []Statement{
915			NewStatement(
916				policy.Allow,
917				NewActionSet(PutObjectAction),
918				NewResourceSet(NewResource("mybucket", "/myobject*")),
919				condition.NewFunctions(func1),
920			),
921			NewStatement(
922				policy.Allow,
923				NewActionSet(PutObjectAction),
924				NewResourceSet(NewResource("mybucket", "/myobject*")),
925				condition.NewFunctions(func2),
926			),
927		},
928	}
929
930	case7Data := []byte(`{
931    "ID": "MyPolicyForMyBucket1",
932    "Version": "2012-10-17",
933    "Statement": [
934        {
935            "Effect": "Allow",
936            "Action": "s3:GetBucketLocation",
937            "Resource": "arn:aws:s3:::mybucket"
938        }
939    ]
940}`)
941
942	case7Policy := Policy{
943		ID:      "MyPolicyForMyBucket1",
944		Version: DefaultVersion,
945		Statements: []Statement{
946			NewStatement(
947				policy.Allow,
948				NewActionSet(GetBucketLocationAction),
949				NewResourceSet(NewResource("mybucket", "")),
950				condition.NewFunctions(),
951			),
952		},
953	}
954
955	case8Data := []byte(`{
956    "ID": "MyPolicyForMyBucket1",
957    "Version": "2012-10-17",
958    "Statement": [
959        {
960            "Effect": "Allow",
961            "Action": "s3:GetBucketLocation",
962            "Resource": "arn:aws:s3:::*"
963        }
964    ]
965}`)
966
967	case8Policy := Policy{
968		ID:      "MyPolicyForMyBucket1",
969		Version: DefaultVersion,
970		Statements: []Statement{
971			NewStatement(
972				policy.Allow,
973				NewActionSet(GetBucketLocationAction),
974				NewResourceSet(NewResource("*", "")),
975				condition.NewFunctions(),
976			),
977		},
978	}
979
980	case9Data := []byte(`{
981    "ID": "MyPolicyForMyBucket1",
982    "Version": "17-10-2012",
983    "Statement": [
984        {
985            "Effect": "Allow",
986            "Action": "s3:PutObject",
987            "Resource": "arn:aws:s3:::mybucket/myobject*"
988        }
989    ]
990}`)
991
992	case10Data := []byte(`{
993    "ID": "MyPolicyForMyBucket1",
994    "Version": "2012-10-17",
995    "Statement": [
996        {
997            "Effect": "Allow",
998            "Action": "s3:PutObject",
999            "Resource": "arn:aws:s3:::mybucket/myobject*"
1000        },
1001        {
1002            "Effect": "Allow",
1003            "Action": "s3:PutObject",
1004            "Resource": "arn:aws:s3:::mybucket/myobject*"
1005        }
1006    ]
1007}`)
1008	case10Policy := Policy{
1009		ID:      "MyPolicyForMyBucket1",
1010		Version: DefaultVersion,
1011		Statements: []Statement{
1012			NewStatement(
1013				policy.Allow,
1014				NewActionSet(PutObjectAction),
1015				NewResourceSet(NewResource("mybucket", "myobject*")),
1016				condition.NewFunctions(),
1017			),
1018		},
1019	}
1020
1021	case11Data := []byte(`{
1022    "ID": "MyPolicyForMyBucket1",
1023    "Version": "2012-10-17",
1024    "Statement": [
1025        {
1026            "Effect": "Allow",
1027            "Action": "s3:PutObject",
1028            "Resource": "arn:aws:s3:::mybucket/myobject*"
1029        },
1030        {
1031            "Effect": "Deny",
1032            "Action": "s3:PutObject",
1033            "Resource": "arn:aws:s3:::mybucket/myobject*"
1034        }
1035    ]
1036}`)
1037
1038	case11Policy := Policy{
1039		ID:      "MyPolicyForMyBucket1",
1040		Version: DefaultVersion,
1041		Statements: []Statement{
1042			NewStatement(
1043				policy.Allow,
1044				NewActionSet(PutObjectAction),
1045				NewResourceSet(NewResource("mybucket", "myobject*")),
1046				condition.NewFunctions(),
1047			),
1048			NewStatement(
1049				policy.Deny,
1050				NewActionSet(PutObjectAction),
1051				NewResourceSet(NewResource("mybucket", "myobject*")),
1052				condition.NewFunctions(),
1053			),
1054		},
1055	}
1056
1057	testCases := []struct {
1058		data                []byte
1059		expectedResult      Policy
1060		expectUnmarshalErr  bool
1061		expectValidationErr bool
1062	}{
1063		{case1Data, case1Policy, false, false},
1064		{case2Data, case2Policy, false, false},
1065		{case3Data, case3Policy, false, false},
1066		{case4Data, case4Policy, false, false},
1067		{case5Data, case5Policy, false, false},
1068		{case6Data, case6Policy, false, false},
1069		{case7Data, case7Policy, false, false},
1070		{case8Data, case8Policy, false, false},
1071		// Invalid version error.
1072		{case9Data, Policy{}, false, true},
1073		// Duplicate statement success, duplicate statement is removed.
1074		{case10Data, case10Policy, false, false},
1075		// Duplicate statement success (Effect differs).
1076		{case11Data, case11Policy, false, false},
1077	}
1078
1079	for i, testCase := range testCases {
1080		var result Policy
1081		err := json.Unmarshal(testCase.data, &result)
1082		expectErr := (err != nil)
1083
1084		if expectErr != testCase.expectUnmarshalErr {
1085			t.Errorf("case %v: error during unmarshal: expected: %v, got: %v", i+1, testCase.expectUnmarshalErr, expectErr)
1086		}
1087
1088		err = result.Validate()
1089		expectErr = (err != nil)
1090
1091		if expectErr != testCase.expectValidationErr {
1092			t.Errorf("case %v: error during validation: expected: %v, got: %v", i+1, testCase.expectValidationErr, expectErr)
1093		}
1094
1095		if !testCase.expectUnmarshalErr && !testCase.expectValidationErr {
1096			if !reflect.DeepEqual(result, testCase.expectedResult) {
1097				t.Errorf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
1098			}
1099		}
1100	}
1101}
1102
1103func TestPolicyValidate(t *testing.T) {
1104	case1Policy := Policy{
1105		Version: DefaultVersion,
1106		Statements: []Statement{
1107			NewStatement(
1108				policy.Allow,
1109				NewActionSet(PutObjectAction),
1110				NewResourceSet(NewResource("", "")),
1111				condition.NewFunctions(),
1112			),
1113		},
1114	}
1115
1116	func1, err := condition.NewNullFunc(
1117		condition.S3XAmzCopySource.ToKey(),
1118		true,
1119	)
1120	if err != nil {
1121		t.Fatalf("unexpected error. %v\n", err)
1122	}
1123	func2, err := condition.NewNullFunc(
1124		condition.S3XAmzServerSideEncryption.ToKey(),
1125		false,
1126	)
1127	if err != nil {
1128		t.Fatalf("unexpected error. %v\n", err)
1129	}
1130	case2Policy := Policy{
1131		ID:      "MyPolicyForMyBucket1",
1132		Version: DefaultVersion,
1133		Statements: []Statement{
1134			NewStatement(
1135				policy.Allow,
1136				NewActionSet(GetObjectAction, PutObjectAction),
1137				NewResourceSet(NewResource("mybucket", "myobject*")),
1138				condition.NewFunctions(func1, func2),
1139			),
1140		},
1141	}
1142
1143	case3Policy := Policy{
1144		ID:      "MyPolicyForMyBucket1",
1145		Version: DefaultVersion,
1146		Statements: []Statement{
1147			NewStatement(
1148				policy.Allow,
1149				NewActionSet(GetObjectAction, PutObjectAction),
1150				NewResourceSet(NewResource("mybucket", "myobject*")),
1151				condition.NewFunctions(),
1152			),
1153		},
1154	}
1155
1156	testCases := []struct {
1157		policy    Policy
1158		expectErr bool
1159	}{
1160		{case1Policy, true},
1161		{case2Policy, true},
1162		{case3Policy, false},
1163	}
1164
1165	for i, testCase := range testCases {
1166		err := testCase.policy.Validate()
1167		expectErr := (err != nil)
1168
1169		if expectErr != testCase.expectErr {
1170			t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
1171		}
1172	}
1173}
1174