Skip to content

Commit 5e8cb01

Browse files
committed
add an auth context
Signed-off-by: Robert Landers <landers.robert@gmail.com>
1 parent 932d784 commit 5e8cb01

13 files changed

Lines changed: 281 additions & 7 deletions

File tree

cli/auth/resourceManager.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"context"
55
"durable_php/appcontext"
66
"durable_php/glue"
7+
"encoding/json"
78
"github.com/modern-go/concurrent"
89
"github.com/nats-io/nats.go"
910
"github.com/nats-io/nats.go/jetstream"
1011
"go.uber.org/zap"
12+
"maps"
1113
"time"
1214
)
1315

@@ -87,6 +89,47 @@ func (r *ResourceManager) DiscoverResource(ctx context.Context, id *glue.StateId
8789
return resource, nil
8890
}
8991

92+
func (r *ResourceManager) ToAuthContext(ctx context.Context, resource *Resource) ([]byte, error) {
93+
var owners []map[string]interface{}
94+
95+
for o, _ := range resource.Owners {
96+
owners = append(owners, map[string]interface{}{
97+
"shareType": "owner",
98+
"subject": string(o),
99+
"allowed": []string{string(Owner)},
100+
})
101+
}
102+
103+
var shares []map[string]interface{}
104+
105+
for _, s := range resource.Shares {
106+
if u, ok := s.(*UserShare); ok {
107+
shares = append(shares, map[string]interface{}{
108+
"shareType": "user",
109+
"subject": string(u.UserId),
110+
"allowed": maps.Keys(u.AllowedOperations),
111+
})
112+
}
113+
if r, ok := s.(*RoleShare); ok {
114+
shares = append(shares, map[string]interface{}{
115+
"shareType": "role",
116+
"subject": string(r.Role),
117+
"allowed": maps.Keys(r.AllowedOperations),
118+
})
119+
}
120+
}
121+
122+
c := map[string]interface{}{
123+
"contextId": map[string]string{
124+
"id": resource.id.String(),
125+
},
126+
"owners": owners,
127+
"shares": shares,
128+
}
129+
130+
return json.Marshal(c)
131+
}
132+
90133
// ScheduleDelete is a method of the ResourceManager struct that is responsible for scheduling the deletion of a
91134
// resource based on the provided context, resource, and time. It deletes the resource from the key-value store and
92135
// publishes a delete message to NATS JetStream with a delay specified by the provided time. The resource is identified

cli/glue/glue.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import (
44
"bytes"
55
"context"
66
"durable_php/appcontext"
7+
"durable_php/auth"
78
"encoding/json"
89
"fmt"
910
"github.com/dunglas/frankenphp"
1011
"github.com/nats-io/nats.go"
1112
"github.com/nats-io/nats.go/jetstream"
1213
"go.uber.org/zap"
1314
"io"
15+
"maps"
1416
"net/http"
1517
"net/url"
1618
"os"
@@ -126,6 +128,17 @@ func (g *Glue) Execute(ctx context.Context, headers http.Header, logger *zap.Log
126128
headers.Add("DPHP_FUNCTION", string(g.function))
127129
headers.Add("DPHP_PAYLOAD", g.payload)
128130

131+
rm := auth.GetResourceManager(ctx, stream)
132+
res, err := rm.DiscoverResource(ctx, id, logger, true)
133+
if err != nil {
134+
logger.Error("DiscoverResource", zap.Error(err))
135+
panic(err)
136+
}
137+
if res != nil {
138+
ac, _ := rm.ToAuthContext(ctx, res)
139+
headers.Add("DPHP_AUTH_CONTEXT", string(ac))
140+
}
141+
129142
provenance := ctx.Value(appcontext.CurrentUserKey)
130143
if provenance != nil {
131144
provenanceJson, err := json.Marshal(provenance)

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"Bottledcode\\DurablePhp\\": "src/"
1111
},
1212
"files": [
13-
"src/functions.php"
13+
"src/functions.php",
14+
"src/Contexts/AuthContext/functions.php"
1415
]
1516
},
1617
"autoload-dev": {

src/Contexts/AuthContext.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Bottledcode\DurablePhp\Contexts;
4+
5+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share;
6+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share\Owner;
7+
use Bottledcode\DurablePhp\State\Ids\StateId;
8+
use Bottledcode\DurablePhp\State\Serializer;
9+
use Crell\Serde\Attributes\SequenceField;
10+
use Withinboredom\Record;
11+
12+
abstract readonly class AuthContext extends Record
13+
{
14+
public StateId $contextId;
15+
16+
/**
17+
* @var array<Owner>
18+
*/
19+
#[SequenceField(arrayType: Owner::class)]
20+
public array $owners;
21+
22+
/**
23+
* @var array<Share>
24+
*/
25+
#[SequenceField(arrayType: Share::class)]
26+
public array $shares;
27+
28+
public static function fromCurrentContext(): ?AuthContext
29+
{
30+
if (isset($_SERVER['HTTP_DPHP_AUTH_CONTEXT'])) {
31+
return Serializer::deserialize($_SERVER['HTTP_DPHP_AUTH_CONTEXT'], self::class);
32+
}
33+
34+
return null;
35+
}
36+
}

src/Contexts/AuthContext/Share.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Bottledcode\DurablePhp\Contexts\AuthContext;
4+
5+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share\Owner;
6+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share\Role;
7+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share\User;
8+
use Bottledcode\DurablePhp\Events\Shares\Operation;
9+
use Crell\Serde\Attributes\SequenceField;
10+
use Crell\Serde\Attributes\StaticTypeMap;
11+
use Withinboredom\Record;
12+
13+
#[StaticTypeMap(key: 'shareType', map: [
14+
'owner' => Owner::class,
15+
'role' => Role::class,
16+
'user' => User::class,
17+
])]
18+
abstract readonly class Share extends Record
19+
{
20+
public string $subject;
21+
22+
/**
23+
* @var array<Operation>
24+
*/
25+
#[SequenceField(arrayType: Operation::class)]
26+
public array $allowed;
27+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* Copyright ©2025 Robert Landers
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the “Software”), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
17+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
21+
* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
22+
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
*/
24+
25+
namespace Bottledcode\DurablePhp\Contexts\AuthContext\Share;
26+
27+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share;
28+
29+
readonly class Owner extends Share {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Bottledcode\DurablePhp\Contexts\AuthContext\Share;
4+
5+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share;
6+
7+
readonly class Role extends Share {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Bottledcode\DurablePhp\Contexts\AuthContext\Share;
4+
5+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share;
6+
7+
readonly class User extends Share {}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Bottledcode\DurablePhp\Contexts\AuthContext;
4+
5+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share\Owner;
6+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share\Role;
7+
use Bottledcode\DurablePhp\Contexts\AuthContext\Share\User;
8+
use Bottledcode\DurablePhp\Events\Shares\Operation;
9+
use ReflectionClass;
10+
11+
function Owner(string $subject): Owner
12+
{
13+
$ref = new ReflectionClass(Owner::class);
14+
15+
return $ref->getMethod('fromArgs')->invoke(null, subject: $subject, allowed: [Operation::Owner]);
16+
}
17+
18+
function Role(string $subject, Operation ...$allowed): Role
19+
{
20+
$ref = new ReflectionClass(Role::class);
21+
22+
return $ref->getMethod('fromArgs')->invoke(null, subject: $subject, allowed: $allowed);
23+
}
24+
25+
function User(string $subject, Operation ...$allowed): User
26+
{
27+
$ref = new ReflectionClass(User::class);
28+
29+
return $ref->getMethod('fromArgs')->invoke(null, subject: $subject, allowed: $allowed);
30+
}

src/EntityContext.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@
2424

2525
namespace Bottledcode\DurablePhp;
2626

27+
use Bottledcode\DurablePhp\Events\GiveOwnership;
2728
use Bottledcode\DurablePhp\Events\RaiseEvent;
29+
use Bottledcode\DurablePhp\Events\RevokeRole;
30+
use Bottledcode\DurablePhp\Events\RevokeUser;
31+
use Bottledcode\DurablePhp\Events\ShareOwnership;
32+
use Bottledcode\DurablePhp\Events\Shares\Operation;
33+
use Bottledcode\DurablePhp\Events\ShareWithRole;
34+
use Bottledcode\DurablePhp\Events\ShareWithUser;
2835
use Bottledcode\DurablePhp\Events\StartExecution;
2936
use Bottledcode\DurablePhp\Events\TaskCompleted;
3037
use Bottledcode\DurablePhp\Events\WithDelay;
@@ -188,4 +195,64 @@ public function currentUserId(): string
188195
{
189196
return $this->user->userId;
190197
}
198+
199+
public function shareOwnership(string $withUser): void
200+
{
201+
$this->eventDispatcher->fire(
202+
WithEntity::forInstance(
203+
StateId::fromEntityId($this->id),
204+
ShareOwnership::withUser($withUser),
205+
),
206+
);
207+
}
208+
209+
public function giveOwnership(string $withUser): void
210+
{
211+
$this->eventDispatcher->fire(
212+
WithEntity::forInstance(
213+
StateId::fromEntityId($this->id),
214+
GiveOwnership::withUser($withUser),
215+
),
216+
);
217+
}
218+
219+
public function grantUser(string $withUser, Operation ...$operation): void
220+
{
221+
$this->eventDispatcher->fire(
222+
WithEntity::forInstance(
223+
StateId::fromEntityId($this->id),
224+
ShareWithUser::For($withUser, ...$operation),
225+
),
226+
);
227+
}
228+
229+
public function grantRole(string $withRole, Operation ...$operation): void
230+
{
231+
$this->eventDispatcher->fire(
232+
WithEntity::forInstance(
233+
StateId::fromEntityId($this->id),
234+
ShareWithRole::For($withRole, ...$operation),
235+
),
236+
);
237+
}
238+
239+
public function revokeUser(string $user): void
240+
{
241+
$this->eventDispatcher->fire(
242+
WithEntity::forInstance(
243+
StateId::fromEntityId($this->id),
244+
RevokeUser::completely($user),
245+
),
246+
);
247+
}
248+
249+
public function revokeRole(string $role): void
250+
{
251+
$this->eventDispatcher->fire(
252+
WithEntity::forInstance(
253+
StateId::fromEntityId($this->id),
254+
RevokeRole::completely($role),
255+
),
256+
);
257+
}
191258
}

0 commit comments

Comments
 (0)