Implemented group actor inbox.

This commit is contained in:
Nick Gerakines 2020-03-30 10:10:11 -04:00
parent 0691aa7577
commit a2c2b11a08
No known key found for this signature in database
GPG Key ID: 33D43D854F96B2E4
7 changed files with 593 additions and 1004 deletions

View File

@ -1 +0,0 @@
package common

View File

@ -44,6 +44,19 @@ func (client ActivityClient) GetSigned(location string, localActor storage.Local
return ldJsonGetSigned(client.HTTPClient, location, signer, localActor.Actor.GetKeyID(), privateKey)
}
func (client ActivityClient) GetSignedWithKey(location, keyID string, privateKey *rsa.PrivateKey) (string, storage.Payload, error) {
client.Logger.Debug("Sending signed activity request", zap.String("url", location))
sigConfig := []httpsig.Algorithm{httpsig.RSA_SHA256}
headersToSign := []string{httpsig.RequestTarget, "date"}
signer, _, err := httpsig.NewSigner(sigConfig, headersToSign, httpsig.Signature)
if err != nil {
return "", nil, err
}
return ldJsonGetSigned(client.HTTPClient, location, signer, keyID, privateKey)
}
func ldJsonGet(client common.HTTPClient, location string) (string, storage.Payload, error) {
request, err := http.NewRequest("GET", location, nil)
if err != nil {

View File

@ -16,17 +16,19 @@ create table if not exists public.groups
unique (name)
);
--- used to track actor to group follow requests
create table if not exists public.group_members
(
id uuid not null
constraint group_members_pk
constraint group_followers_pk
primary key,
created_at timestamp with time zone not null,
updated_at timestamp with time zone not null,
group_actor_id uuid not null,
member_actor_id uuid not null,
activity jsonb not null,
membership_type integer default 0 not null,
constraint group_members_actors_uindex
status integer default 0 not null,
role integer default 0 not null,
constraint group_followers_uindex
unique (group_actor_id, member_actor_id)
);

View File

@ -2,6 +2,9 @@ package storage
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"strings"
"time"
@ -14,39 +17,54 @@ import (
type GroupStorage interface {
RecordGroup(ctx context.Context, userRowID, actorRowID uuid.UUID, name, publicKey, privateKey, displayName, about string) (uuid.UUID, error)
RecordGroupAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, userRowID, actorRowID uuid.UUID, name, publicKey, privateKey, displayName, about string) (uuid.UUID, error)
RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error)
RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error)
GetGroupByName(ctx context.Context, name string) (Group, error)
GetGroupByID(ctx context.Context, groupRowID uuid.UUID) (Group, error)
GetGroupsByOwner(ctx context.Context, userRowID uuid.UUID) ([]Group, error)
ListGroups(ctx context.Context) ([]Group, error)
GroupNameAvailable(context.Context, string) (bool, error)
CountGroupMembers(ctx context.Context, groupActorRowID uuid.UUID) (int, error)
GroupMemberActorIDs(ctx context.Context, groupActorRowID uuid.UUID) ([]string, error)
RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error)
RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error)
UpdateGroupMemberStatus(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, status RelationshipStatus) error
UpdateGroupMemberRole(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, role GroupRole) error
GroupActorsForMemberActorRowID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error)
GroupMemberActorsForGroupActorID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error)
RemoveGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) error
GroupMemberCanSubmit(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) (bool, error)
}
type Group struct {
ID uuid.UUID
CreatedAt time.Time
UpdatedAt time.Time
Name string
PublicKey string
PrivateKey string
DisplayName string
About string
AcceptFollowers bool
OwnerID uuid.UUID
ActorID uuid.UUID
ID uuid.UUID
CreatedAt time.Time
UpdatedAt time.Time
Name string
PublicKey string
PrivateKey string
DisplayName string
About string
AcceptFollowers bool
DefaultMemberRole GroupRole
OwnerID uuid.UUID
ActorID uuid.UUID
}
type GroupRole int16
const (
// GroupOwner can manage the group.
GroupOwner = 1
GroupOwner GroupRole = 3
// GroupMember can post to the group.
GroupMember = 2
GroupMember GroupRole = 1
// GroupViewer can receive group activities, but cannot create group activities.
GroupViewer = 3
GroupViewer GroupRole = 0
)
var groupFields = []string{
@ -59,10 +77,29 @@ var groupFields = []string{
"display_name",
"about",
"accept_followers",
"default_member_role",
"owner",
"actor_id",
}
func (g Group) GetPrivateKey() (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(g.PrivateKey))
if block == nil {
return nil, errors.New("invalid RSA PEM")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return key, nil
}
func (g Group) GetDecodedPublicKey() (*rsa.PublicKey, error) {
return DecodePublicKey(g.PublicKey)
}
func (s pgStorage) RecordGroup(ctx context.Context, userRowID, actorRowID uuid.UUID, name, publicKey, privateKey, displayName, about string) (uuid.UUID, error) {
rowID := NewV4()
now := s.now()
@ -76,16 +113,16 @@ func (s pgStorage) RecordGroupAll(ctx context.Context, rowID uuid.UUID, createdA
return id, errors.WrapGroupUpsertFailedError(err)
}
func (s pgStorage) RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error) {
func (s pgStorage) RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error) {
rowID := NewV4()
now := s.now()
return s.RecordGroupMemberAll(ctx, rowID, now, now, groupActorRowID, memberActorRowID, activity, membershipType)
return s.RecordGroupMemberAll(ctx, rowID, now, now, groupActorRowID, memberActorRowID, activity, status, role)
}
func (s pgStorage) RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error) {
query := `INSERT INTO group_members (id, created_at, updated_at, group_actor_id, member_actor_id, activity, membership_type) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT ON CONSTRAINT group_members_actors_uindex DO UPDATE set updated_at = $3 RETURNING id`
func (s pgStorage) RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error) {
query := `INSERT INTO group_members (id, created_at, updated_at, group_actor_id, member_actor_id, activity, relationship_status, member_role) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT ON CONSTRAINT group_members_actors_uindex DO UPDATE set updated_at = $3 RETURNING id`
var id uuid.UUID
err := s.db.QueryRowContext(ctx, query, rowID, createdAt, updatedAt, groupActorRowID, memberActorRowID, activity, membershipType).Scan(&id)
err := s.db.QueryRowContext(ctx, query, rowID, createdAt, updatedAt, groupActorRowID, memberActorRowID, activity, status, role).Scan(&id)
return id, errors.WrapGroupMemberUpsertFailedError(err)
}
@ -138,6 +175,7 @@ func (s pgStorage) getGroups(ctx context.Context, query string, args ...interfac
&group.DisplayName,
&group.About,
&group.AcceptFollowers,
&group.DefaultMemberRole,
&group.OwnerID,
&group.ActorID)
if err != nil {
@ -192,7 +230,34 @@ func (s pgStorage) GroupMemberActorIDs(ctx context.Context, groupActorRowID uuid
return actors, nil
}
func (s pgStorage) GroupActorsForMemberActorRowID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error) {
func (s pgStorage) GroupActorsForMemberActorRowID(ctx context.Context, memberActorRowID uuid.UUID) ([]*Actor, error) {
query := actorsSelectQuery("group_members gm ON a.id = gm.group_actor_id", []string{"gm.member_actor_id = $1"})
return s.getActors(ctx, query, memberActorRowID)
}
func (s pgStorage) GroupMemberActorsForGroupActorID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error) {
query := actorsSelectQuery("group_members gm ON a.id = gm.member_actor_id", []string{"gm.group_actor_id = $1"})
return s.getActors(ctx, query, groupActorRowID)
}
func (s pgStorage) UpdateGroupMemberStatus(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, status RelationshipStatus) error {
now := s.now()
_, err := s.db.ExecContext(ctx, "UPDATE group_members SET relationship_status = $4, updated_at = $3 WHERE group_actor_id = $1 AND member_actor_id = $2", groupActorRowID, memberActorRowID, now, status)
return errors.WrapUserUpdateFailedError(err)
}
func (s pgStorage) UpdateGroupMemberRole(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, role GroupRole) error {
now := s.now()
_, err := s.db.ExecContext(ctx, "UPDATE group_members SET member_role = $4, updated_at = $3 WHERE group_actor_id = $1 AND member_actor_id = $2", groupActorRowID, memberActorRowID, now, role)
return errors.WrapUserUpdateFailedError(err)
}
func (s pgStorage) RemoveGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) error {
_, err := s.db.ExecContext(ctx, `DELETE FROM group_members WHERE group_actor_id = $1 AND member_actor_id = $2`, groupActorRowID, memberActorRowID)
return errors.WrapNetworkRelationshipUpdateFailedError(err)
}
func (s pgStorage) GroupMemberCanSubmit(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) (bool, error) {
query := `SELECT COUNT(*) FROM group_members WHERE group_actor_id = $1 AND member_actor_id = $2 AND member_role IN ($3, $4)`
return s.wrappedExists(errors.WrapGroupMemberQueryFailedError, ctx, query, groupActorRowID, memberActorRowID, GroupMember, GroupOwner)
}

View File

@ -49,35 +49,15 @@
{{ .ActorID }}
</td>
<td>
<a class="btn btn-sm btn-outline-primary" href="{{ url "group" .Name }}">
<a href="{{ url "group" .Name }}">
View
</a>
<a class="btn btn-sm btn-outline-primary" href="{{ url "group_manage" .Name }}">
<a href="{{ url "group_manage" .Name }}">
Manage
</a>
</td>
</tr>
{{ end }}
{{ range .pending_following }}
<tr>
<td><span class="badge badge-danger">Pending</span> {{ .ActorID }}</td>
</tr>
{{ end }}
{{ range .invitations }}
<tr>
<td><span class="badge badge-primary">Pending</span> {{ .ActorID }}</td>
<td>
<form method="post" action="{{ url "groups_accept" }}">
<input type="hidden" name="actor" value="{{ . }}"/>
<input class="btn btn-sm btn-success" type="submit" name="submit" value="Accept"/>
</form>
<form method="post" action="{{ url "groups_reject" }}">
<input type="hidden" name="actor" value="{{ . }}"/>
<input class="btn btn-sm btn-danger" type="submit" name="submit" value="Reject"/>
</form>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>

View File

@ -120,15 +120,6 @@ func (h handler) dashboardGroupsCreate(c *gin.Context) {
return err
}
joinGroup := storage.EmptyPayload()
joinGroup["@context"] = "https://www.w3.org/ns/activitystreams"
joinGroup["id"] = common.ActivityURL(h.domain, storage.NewV4())
joinGroup["summary"] = ""
joinGroup["type"] = "Join"
joinGroup["actor"] = localUserActor.ActorID
joinGroup["object"] = groupActorID
joinGroup["published"] = publishedAt
err = tx.RecordActorKey(ctx, actorRowID, groupKeyID, publicKey)
if err != nil {
return err
@ -141,7 +132,17 @@ func (h handler) dashboardGroupsCreate(c *gin.Context) {
return err
}
_, err = tx.RecordGroupMember(ctx, actorRowID, localUser.ActorID, joinGroup, storage.GroupOwner)
followGroup := storage.EmptyPayload()
followGroup["@context"] = "https://www.w3.org/ns/activitystreams"
followGroup["id"] = common.ActivityURL(h.domain, storage.NewV4())
followGroup["summary"] = ""
followGroup["type"] = "Follow"
followGroup["actor"] = localUserActor.ActorID
followGroup["to"] = []string{localUserActor.ActorID}
followGroup["object"] = groupActorID
followGroup["published"] = publishedAt
_, err = tx.RecordGroupMember(ctx, actorRowID, localUser.ActorID, followGroup, storage.AcceptRelationshipStatus, storage.GroupOwner)
if err != nil {
return err
}

File diff suppressed because it is too large Load Diff