Skip to content

Commit b2f3820

Browse files
authored
Custom Policies for Buckets (#1332)
* custom policies * fixing error * add formatting
1 parent 10b8a93 commit b2f3820

File tree

9 files changed

+116
-46
lines changed

9 files changed

+116
-46
lines changed

models/bucket.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

models/set_bucket_policy_request.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

portal-ui/src/screens/Console/Buckets/BucketDetails/BucketSummaryPanel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,11 @@ const BucketSummary = ({
178178
const bucketName = match.params["bucketName"];
179179

180180
let accessPolicy = "n/a";
181+
let policyDefinition = "";
181182

182183
if (bucketInfo !== null) {
183184
accessPolicy = bucketInfo.access;
185+
policyDefinition = bucketInfo.definition;
184186
}
185187

186188
const displayGetBucketObjectLockConfiguration = hasPermission(bucketName, [
@@ -485,6 +487,7 @@ const BucketSummary = ({
485487
bucketName={bucketName}
486488
open={accessPolicyScreenOpen}
487489
actualPolicy={accessPolicy}
490+
actualDefinition={policyDefinition}
488491
closeModalAndRefresh={closeSetAccessPolicy}
489492
/>
490493
)}

portal-ui/src/screens/Console/Buckets/BucketDetails/SetAccessPolicy.tsx

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,37 @@ import api from "../../../../common/api";
3131
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
3232
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
3333
import { ChangeAccessPolicyIcon } from "../../../../icons";
34+
import CodeMirrorWrapper from "../../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
3435

3536
const styles = (theme: Theme) =>
3637
createStyles({
38+
codeMirrorContainer: {
39+
marginBottom: 20,
40+
paddingLeft: 15,
41+
"&:nth-child(2) .MuiGrid-root:nth-child(3)": {
42+
border: "1px solid #EAEAEA",
43+
},
44+
"& label": {
45+
marginBottom: ".5rem",
46+
},
47+
"& label + div": {
48+
display: "none",
49+
},
50+
},
3751
...modalStyleUtils,
3852
...spacingUtils,
3953
});
54+
createStyles({
55+
...modalStyleUtils,
56+
...spacingUtils,
57+
});
4058

4159
interface ISetAccessPolicyProps {
4260
classes: any;
4361
open: boolean;
4462
bucketName: string;
4563
actualPolicy: string;
64+
actualDefinition: string;
4665
closeModalAndRefresh: () => void;
4766
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
4867
}
@@ -52,11 +71,13 @@ const SetAccessPolicy = ({
5271
open,
5372
bucketName,
5473
actualPolicy,
74+
actualDefinition,
5575
closeModalAndRefresh,
5676
setModalErrorSnackMessage,
5777
}: ISetAccessPolicyProps) => {
5878
const [addLoading, setAddLoading] = useState<boolean>(false);
5979
const [accessPolicy, setAccessPolicy] = useState<string>("");
80+
const [policyDefinition, setPolicyDefinition] = useState<string>("");
6081
const addRecord = (event: React.FormEvent) => {
6182
event.preventDefault();
6283
if (addLoading) {
@@ -66,6 +87,7 @@ const SetAccessPolicy = ({
6687
api
6788
.invoke("PUT", `/api/v1/buckets/${bucketName}/set-policy`, {
6889
access: accessPolicy,
90+
definition: policyDefinition,
6991
})
7092
.then((res) => {
7193
setAddLoading(false);
@@ -79,7 +101,12 @@ const SetAccessPolicy = ({
79101

80102
useEffect(() => {
81103
setAccessPolicy(actualPolicy);
82-
}, [setAccessPolicy, actualPolicy]);
104+
setPolicyDefinition(
105+
actualDefinition
106+
? JSON.stringify(JSON.parse(actualDefinition), null, 4)
107+
: ""
108+
);
109+
}, [setAccessPolicy, actualPolicy, setPolicyDefinition, actualDefinition]);
83110

84111
return (
85112
<ModalWrapper
@@ -98,24 +125,34 @@ const SetAccessPolicy = ({
98125
}}
99126
>
100127
<Grid container>
101-
<Grid
102-
item
103-
xs={12}
104-
className={`${classes.spacerTop} ${classes.formFieldRow}`}
105-
>
106-
<SelectWrapper
107-
value={accessPolicy}
108-
label="Access Policy"
109-
id="select-access-policy"
110-
name="select-access-policy"
111-
onChange={(e: SelectChangeEvent<string>) => {
112-
setAccessPolicy(e.target.value as string);
113-
}}
114-
options={[
115-
{ value: "PRIVATE", label: "Private" },
116-
{ value: "PUBLIC", label: "Public" },
117-
]}
118-
/>
128+
<Grid item xs={12} className={classes.modalFormScrollable}>
129+
<Grid item xs={12} className={classes.formFieldRow}>
130+
<SelectWrapper
131+
value={accessPolicy}
132+
label="Access Policy"
133+
id="select-access-policy"
134+
name="select-access-policy"
135+
onChange={(e: SelectChangeEvent<string>) => {
136+
setAccessPolicy(e.target.value as string);
137+
}}
138+
options={[
139+
{ value: "PRIVATE", label: "Private" },
140+
{ value: "PUBLIC", label: "Public" },
141+
{ value: "CUSTOM", label: "Custom" },
142+
]}
143+
/>
144+
</Grid>
145+
{accessPolicy === "CUSTOM" && (
146+
<Grid item xs={12} className={classes.codeMirrorContainer}>
147+
<CodeMirrorWrapper
148+
label={`Write Policy`}
149+
value={policyDefinition}
150+
onBeforeChange={(editor, data, value) => {
151+
setPolicyDefinition(value);
152+
}}
153+
/>
154+
</Grid>
155+
)}
119156
</Grid>
120157
<Grid item xs={12} className={classes.modalButtonBar}>
121158
<Button
@@ -133,7 +170,9 @@ const SetAccessPolicy = ({
133170
type="submit"
134171
variant="contained"
135172
color="primary"
136-
disabled={addLoading}
173+
disabled={
174+
addLoading || (accessPolicy === "CUSTOM" && !policyDefinition)
175+
}
137176
>
138177
Set
139178
</Button>

portal-ui/src/screens/Console/Buckets/types.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface Details {
4141
export interface BucketInfo {
4242
name: string;
4343
access: string;
44+
definition: string;
4445
}
4546

4647
export interface BucketList {

restapi/embedded_spec.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

restapi/user_buckets.go

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -436,26 +436,29 @@ func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketReque
436436
}
437437

438438
// setBucketAccessPolicy set the access permissions on an existing bucket.
439-
func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName string, access models.BucketAccess) error {
439+
func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName string, access models.BucketAccess, policyDefinition string) error {
440440
if strings.TrimSpace(bucketName) == "" {
441441
return fmt.Errorf("error: bucket name not present")
442442
}
443443
if strings.TrimSpace(string(access)) == "" {
444444
return fmt.Errorf("error: bucket access not present")
445445
}
446446
// Prepare policyJSON corresponding to the access type
447-
if access != models.BucketAccessPRIVATE && access != models.BucketAccessPUBLIC {
447+
if access != models.BucketAccessPRIVATE && access != models.BucketAccessPUBLIC && access != models.BucketAccessCUSTOM {
448448
return fmt.Errorf("access: `%s` not supported", access)
449449
}
450-
bucketPolicy := consoleAccess2policyAccess(access)
451450

452451
bucketAccessPolicy := policy.BucketAccessPolicy{Version: minioIAMPolicy.DefaultVersion}
452+
if access == models.BucketAccessCUSTOM {
453+
err := client.setBucketPolicyWithContext(ctx, bucketName, policyDefinition)
454+
if err != nil {
455+
return err
456+
}
457+
return nil
458+
}
459+
bucketPolicy := consoleAccess2policyAccess(access)
453460
bucketAccessPolicy.Statements = policy.SetPolicy(bucketAccessPolicy.Statements,
454461
bucketPolicy, bucketName, "")
455-
// implemented like minio/mc/ s3Client.SetAccess()
456-
if len(bucketAccessPolicy.Statements) == 0 {
457-
return client.setBucketPolicyWithContext(ctx, bucketName, "")
458-
}
459462
policyJSON, err := json.Marshal(bucketAccessPolicy)
460463
if err != nil {
461464
return err
@@ -469,18 +472,19 @@ func getBucketSetPolicyResponse(session *models.Principal, bucketName string, re
469472
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
470473
defer cancel()
471474

475+
// get updated bucket details and return it
472476
mClient, err := newMinioClient(session)
473477
if err != nil {
474478
return nil, prepareError(err)
475479
}
476480
// create a minioClient interface implementation
477481
// defining the client to be used
478482
minioClient := minioClient{client: mClient}
479-
// set bucket access policy
480-
if err := setBucketAccessPolicy(ctx, minioClient, bucketName, *req.Access); err != nil {
483+
484+
if err := setBucketAccessPolicy(ctx, minioClient, bucketName, *req.Access, req.Definition); err != nil {
481485
return nil, prepareError(err)
482486
}
483-
// get updated bucket details and return it
487+
// set bucket access policy
484488
bucket, err := getBucketInfo(ctx, minioClient, bucketName)
485489
if err != nil {
486490
return nil, prepareError(err)
@@ -548,19 +552,19 @@ func getBucketInfo(ctx context.Context, client MinioClient, bucketName string) (
548552
LogError("error getting bucket policy: %v", err)
549553
}
550554

551-
var policyAccess policy.BucketPolicy
552555
if policyStr == "" {
553-
policyAccess = policy.BucketPolicyNone
556+
bucketAccess = models.BucketAccessPRIVATE
554557
} else {
555558
var p policy.BucketAccessPolicy
556559
if err = json.Unmarshal([]byte(policyStr), &p); err != nil {
557560
return nil, err
558561
}
559-
policyAccess = policy.GetPolicy(p.Statements, bucketName, "")
560-
}
561-
bucketAccess = policyAccess2consoleAccess(policyAccess)
562-
if bucketAccess == models.BucketAccessPRIVATE && policyStr != "" {
563-
bucketAccess = models.BucketAccessCUSTOM
562+
policyAccess := policy.GetPolicy(p.Statements, bucketName, "")
563+
if len(p.Statements) > 0 && policyAccess == policy.BucketPolicyNone {
564+
bucketAccess = models.BucketAccessCUSTOM
565+
} else {
566+
bucketAccess = policyAccess2consoleAccess(policyAccess)
567+
}
564568
}
565569
bucketTags, err := client.GetBucketTagging(ctx, bucketName)
566570
if err != nil {
@@ -574,6 +578,7 @@ func getBucketInfo(ctx context.Context, client MinioClient, bucketName string) (
574578
return &models.Bucket{
575579
Name: &bucketName,
576580
Access: &bucketAccess,
581+
Definition: policyStr,
577582
CreationDate: "", // to be implemented
578583
Size: 0, // to be implemented
579584
Details: bucketDetails,

restapi/user_buckets_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,16 +338,16 @@ func TestBucketInfo(t *testing.T) {
338338
assert.Equal(outputExpected.CreationDate, bucketInfo.CreationDate)
339339
assert.Equal(outputExpected.Size, bucketInfo.Size)
340340

341-
// Test-3: getBucketInfo() get a bucket with CUSTOM access
342-
// if bucket has a custom policy set it should return CUSTOM
341+
// Test-3: getBucketInfo() get a bucket with PRIVATE access
342+
// if bucket has a null statement, the the bucket is PRIVATE
343343
mockPolicy = "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
344344
minioGetBucketPolicyMock = func(bucketName string) (string, error) {
345345
return mockPolicy, nil
346346
}
347347
bucketToSet = "csbucket"
348348
outputExpected = &models.Bucket{
349349
Name: swag.String(bucketToSet),
350-
Access: models.NewBucketAccess(models.BucketAccessCUSTOM),
350+
Access: models.NewBucketAccess(models.BucketAccessPRIVATE),
351351
CreationDate: "", // to be implemented
352352
Size: 0, // to be implemented
353353
}
@@ -393,35 +393,35 @@ func TestSetBucketAccess(t *testing.T) {
393393
minioSetBucketPolicyWithContextMock = func(ctx context.Context, bucketName, policy string) error {
394394
return nil
395395
}
396-
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC); err != nil {
396+
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC, ""); err != nil {
397397
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
398398
}
399399

400400
// Test-2: setBucketAccessPolicy() set private access
401-
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPRIVATE); err != nil {
401+
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPRIVATE, ""); err != nil {
402402
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
403403
}
404404

405405
// Test-3: setBucketAccessPolicy() set invalid access, expected error
406-
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "other"); assert.Error(err) {
406+
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "other", ""); assert.Error(err) {
407407
assert.Equal("access: `other` not supported", err.Error())
408408
}
409409

410410
// Test-4: setBucketAccessPolicy() set access on empty bucket name, expected error
411-
if err := setBucketAccessPolicy(ctx, minClient, "", models.BucketAccessPRIVATE); assert.Error(err) {
411+
if err := setBucketAccessPolicy(ctx, minClient, "", models.BucketAccessPRIVATE, ""); assert.Error(err) {
412412
assert.Equal("error: bucket name not present", err.Error())
413413
}
414414

415415
// Test-5: setBucketAccessPolicy() set empty access on bucket, expected error
416-
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", ""); assert.Error(err) {
416+
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "", ""); assert.Error(err) {
417417
assert.Equal("error: bucket access not present", err.Error())
418418
}
419419

420420
// Test-5: setBucketAccessPolicy() handle error on setPolicy call
421421
minioSetBucketPolicyWithContextMock = func(ctx context.Context, bucketName, policy string) error {
422422
return errors.New("error")
423423
}
424-
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC); assert.Error(err) {
424+
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC, ""); assert.Error(err) {
425425
assert.Equal("error", err.Error())
426426
}
427427

0 commit comments

Comments
 (0)