diff --git a/RelayConf/relayconf.go b/RelayConf/relayconf.go new file mode 100644 index 0000000..269187c --- /dev/null +++ b/RelayConf/relayconf.go @@ -0,0 +1,35 @@ +package relayconf + +import "github.com/go-redis/redis" + +// RelayConfig : struct for relay configuration +type RelayConfig struct { + BlockService bool + ManuallyAccept bool +} + +// LoadConfig : Loader for relay configuration +func LoadConfig(redClient *redis.Client) RelayConfig { + blockService, err := redClient.HGet("relay:config", "block_service").Result() + if err != nil { + redClient.HSet("relay:config", "block_service", 0) + blockService = "0" + } + manuallyAccept, err := redClient.HGet("relay:config", "manually_accept").Result() + if err != nil { + redClient.HSet("relay:config", "manually_accept", 0) + manuallyAccept = "0" + } + return RelayConfig{ + BlockService: blockService == "1", + ManuallyAccept: manuallyAccept == "1", + } +} + +func SetConfig(redClient *redis.Client, key string, value bool) { + strValue := 0 + if value { + strValue = 1 + } + redClient.HSet("relay:config", key, strValue) +} diff --git a/cli/cli.go b/cli/cli.go index e8f9f6f..50d2190 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -1,126 +1,174 @@ package main import ( + "crypto/rsa" "fmt" "log" + "net/url" "os" - "strings" + machinery "github.com/RichardKnop/machinery/v1" + "github.com/RichardKnop/machinery/v1/config" "github.com/go-redis/redis" "github.com/urfave/cli" + "github.com/yukimochi/Activity-Relay/KeyLoader" ) +var hostname *url.URL +var hostkey *rsa.PrivateKey var redClient *redis.Client - -func listDomain(c *cli.Context) error { - var err error - var domains []string - var message string - switch c.String("type") { - case "limited": - message = " - Limited domain :" - domains, err = redClient.HKeys("relay:config:limitedDomain").Result() - if err != nil { - return err - } - case "blocked": - message = " - Blocked domain :" - domains, err = redClient.HKeys("relay:config:blockedDomain").Result() - if err != nil { - return err - } - default: - message = " - Subscribed domain :" - temp, err := redClient.Keys("relay:subscription:*").Result() - if err != nil { - return err - } - for _, domain := range temp { - domains = append(domains, strings.Replace(domain, "relay:subscription:", "", 1)) - } - } - fmt.Println(message) - for _, domain := range domains { - fmt.Println(domain) - } - fmt.Println(fmt.Sprintf("Total : %d", len(domains))) - return nil -} - -func manageDomain(c *cli.Context) error { - if c.String("domain") == "" { - fmt.Println("No domain given.") - return nil - } - switch c.String("type") { - case "limited": - if c.Bool("undo") { - redClient.HDel("relay:config:limitedDomain", c.String("domain")) - fmt.Println("Unregistrate [" + c.String("domain") + "] from Limited domain.") - } else { - redClient.HSet("relay:config:limitedDomain", c.String("domain"), "1") - fmt.Println("Registrate [" + c.String("domain") + "] as Limited domain.") - } - case "blocked": - if c.Bool("undo") { - redClient.HDel("relay:config:blockedDomain", c.String("domain")) - fmt.Println("Unregistrate [" + c.String("domain") + "] from Blocked domain.") - } else { - redClient.HSet("relay:config:blockedDomain", c.String("domain"), "1") - fmt.Println("Registrate [" + c.String("domain") + "] as Blocked domain.") - } - default: - fmt.Println("No type given.") - } - return nil -} +var macServer *machinery.Server func main() { + pemPath := os.Getenv("ACTOR_PEM") + if pemPath == "" { + panic("Require ACTOR_PEM environment variable.") + } + relayDomain := os.Getenv("RELAY_DOMAIN") + if relayDomain == "" { + panic("Require RELAY_DOMAIN environment variable.") + } + redisURL := os.Getenv("REDIS_URL") + if redisURL == "" { + redisURL = "127.0.0.1:6379" + } + + var err error + hostkey, err = keyloader.ReadPrivateKeyRSAfromPath(pemPath) + if err != nil { + panic("Can't read Hostkey Pemfile") + } + hostname, err = url.Parse("https://" + relayDomain) + if err != nil { + panic("Can't parse Relay Domain") + } redClient = redis.NewClient(&redis.Options{ - Addr: os.Getenv("REDIS_URL"), + Addr: redisURL, }) + var macConfig = &config.Config{ + Broker: "redis://" + redisURL, + DefaultQueue: "relay", + ResultBackend: "redis://" + redisURL, + ResultsExpireIn: 5, + } + + macServer, err = machinery.NewServer(macConfig) + if err != nil { + fmt.Println(err) + } + app := cli.NewApp() app.Name = "Activity Relay Extarnal CLI" app.Usage = "Control Relay configration" app.Version = "0.0.2" app.Commands = []cli.Command{ { - Name: "list-domain", - Aliases: []string{"ld"}, - Usage: "List {subscribed,limited,blocked} domains", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "type, t", - Value: "subscribed", - Usage: "Registrate type [subscribed,limited,blocked]", + Name: "domain", + Usage: "Management domains", + Subcommands: []cli.Command{ + { + Name: "list", + Usage: "List {subscribed,limited,blocked} domains", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "type, t", + Value: "subscribed", + Usage: "Domain type [subscribed,limited,blocked]", + }, + }, + Action: listDomains, + }, + { + Name: "set", + Usage: "set domain type [limited,blocked]", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "type, t", + Usage: "Domain type [limited,blocked]", + }, + cli.StringFlag{ + Name: "domain, d", + Usage: "Registrate domain", + }, + cli.BoolFlag{ + Name: "undo, u", + Usage: "Undo registrate", + }, + }, + Action: setDomainType, }, }, - Action: listDomain, }, { - Name: "manage-domain", - Aliases: []string{"md"}, - Usage: "Manage {limited,blocked} domains", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "type, t", - Usage: "Registrate type [limited,blocked]", + Name: "config", + Usage: "Management relay config", + Subcommands: []cli.Command{ + { + Name: "show", + Usage: "Show all relay configrations", + Action: listConfigs, }, - cli.StringFlag{ - Name: "domain, d", - Usage: "Registrate domain", + { + Name: "service-block", + Usage: "Enable blocking for service-type actor", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "undo, u", + Usage: "Undo block", + }, + }, + Action: serviceBlock, }, - cli.BoolFlag{ - Name: "undo, u", - Usage: "Undo registrate", + { + Name: "manually-accept", + Usage: "Enable Manually accept follow-request", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "undo, u", + Usage: "Undo block", + }, + }, + Action: manuallyAccept, + }, + }, + }, + { + Name: "follow-request", + Usage: "Management follow-request", + Subcommands: []cli.Command{ + { + Name: "show", + Usage: "Show all follow-request", + Action: listFollows, + }, + { + Name: "reject", + Usage: "Reject follow-request", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "domain, d", + Usage: "domain name", + }, + }, + Action: rejectFollow, + }, + { + Name: "accept", + Usage: "Accept follow-request", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "domain, d", + Usage: "domain name", + }, + }, + Action: acceptFollow, }, }, - Action: manageDomain, }, } - err := app.Run(os.Args) + err = app.Run(os.Args) if err != nil { log.Fatal(err) } diff --git a/cli/config.go b/cli/config.go new file mode 100644 index 0000000..3525314 --- /dev/null +++ b/cli/config.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + + "github.com/urfave/cli" + "github.com/yukimochi/Activity-Relay/RelayConf" +) + +func serviceBlock(c *cli.Context) { + if c.Bool("undo") { + relayconf.SetConfig(redClient, "block_service", false) + fmt.Println("Blocking for service-type actor is Disabled.") + } else { + relayconf.SetConfig(redClient, "block_service", true) + fmt.Println("Blocking for service-type actor is Enabled.") + } +} + +func manuallyAccept(c *cli.Context) { + if c.Bool("undo") { + relayconf.SetConfig(redClient, "manually_accept", false) + fmt.Println("Manually accept follow-request is Disabled.") + } else { + relayconf.SetConfig(redClient, "manually_accept", true) + fmt.Println("Manually accept follow-request is Enabled.") + } +} + +func listConfigs(c *cli.Context) { + config := relayconf.LoadConfig(redClient) + + fmt.Println("Blocking for service-type actor : ", config.BlockService) + fmt.Println("Manually accept follow-request : ", config.ManuallyAccept) +} diff --git a/cli/domain.go b/cli/domain.go new file mode 100644 index 0000000..39b4d21 --- /dev/null +++ b/cli/domain.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/urfave/cli" +) + +func listDomains(c *cli.Context) error { + var err error + var domains []string + var message string + switch c.String("type") { + case "limited": + fmt.Println(" - Limited domain :") + domains, err = redClient.HKeys("relay:config:limitedDomain").Result() + if err != nil { + return err + } + case "blocked": + fmt.Println(" - Blocked domain :") + domains, err = redClient.HKeys("relay:config:blockedDomain").Result() + if err != nil { + return err + } + default: + fmt.Println(" - Subscribed domain :") + temp, err := redClient.Keys("relay:subscription:*").Result() + if err != nil { + return err + } + for _, domain := range temp { + domains = append(domains, strings.Replace(domain, "relay:subscription:", "", 1)) + } + } + fmt.Println(message) + for _, domain := range domains { + fmt.Println(domain) + } + fmt.Println(fmt.Sprintf("Total : %d", len(domains))) + return nil +} + +func setDomainType(c *cli.Context) error { + if c.String("domain") == "" { + fmt.Println("No domain given.") + return nil + } + switch c.String("type") { + case "limited": + if c.Bool("undo") { + redClient.HDel("relay:config:limitedDomain", c.String("domain")) + fmt.Println("Unset [" + c.String("domain") + "] as Limited domain.") + } else { + redClient.HSet("relay:config:limitedDomain", c.String("domain"), "1") + fmt.Println("Set [" + c.String("domain") + "] as Limited domain.") + } + case "blocked": + if c.Bool("undo") { + redClient.HDel("relay:config:blockedDomain", c.String("domain")) + fmt.Println("Unset [" + c.String("domain") + "] as Blocked domain.") + } else { + redClient.HSet("relay:config:blockedDomain", c.String("domain"), "1") + fmt.Println("Set [" + c.String("domain") + "] as Blocked domain.") + } + default: + fmt.Println("No type given.") + } + return nil +} diff --git a/cli/follow.go b/cli/follow.go new file mode 100644 index 0000000..5b57d23 --- /dev/null +++ b/cli/follow.go @@ -0,0 +1,135 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/url" + "strings" + "unsafe" + + "github.com/RichardKnop/machinery/v1/tasks" + "github.com/urfave/cli" + "github.com/yukimochi/Activity-Relay/ActivityPub" +) + +func pushRegistorJob(inboxURL string, body []byte) { + job := &tasks.Signature{ + Name: "registor", + RetryCount: 25, + Args: []tasks.Arg{ + { + Name: "inboxURL", + Type: "string", + Value: inboxURL, + }, + { + Name: "body", + Type: "string", + Value: *(*string)(unsafe.Pointer(&body)), + }, + }, + } + _, err := macServer.SendTask(job) + if err != nil { + fmt.Println(err) + } +} + +func listFollows(c *cli.Context) error { + var err error + var domains []string + + fmt.Println(" - Follow request :") + follows, err := redClient.Keys("relay:pending:*").Result() + if err != nil { + return err + } + for _, follow := range follows { + domains = append(domains, strings.Replace(follow, "relay:pending:", "", 1)) + } + for _, domain := range domains { + fmt.Println(domain) + } + return nil +} + +func acceptFollow(c *cli.Context) error { + domain := c.String("domain") + if domain != "" { + num, err := redClient.Exists("relay:pending:" + domain).Result() + if err != nil { + return err + } + if num == 0 { + fmt.Println("Given domain not found.") + return nil + } + + fmt.Println("Accept Follow request : " + domain) + data, err := redClient.HGetAll("relay:pending:" + domain).Result() + if err != nil { + return err + } + activity := activitypub.Activity{ + []string{"https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"}, + data["activity_id"], + data["actor"], + data["type"], + data["object"], + nil, + nil, + } + + actorDomain, _ := url.Parse(activity.Actor) + resp := activitypub.GenerateActivityResponse(hostname, actorDomain, "Accept", activity) + jsonData, _ := json.Marshal(&resp) + + pushRegistorJob(data["inbox_url"], jsonData) + redClient.HSet("relay:subscription:"+domain, "inbox_url", data["inbox_url"]) + redClient.Del("relay:pending:" + domain) + return nil + } else { + fmt.Println("No domain given.") + return nil + } +} + +func rejectFollow(c *cli.Context) error { + domain := c.String("domain") + if domain != "" { + num, err := redClient.Exists("relay:pending:" + domain).Result() + if err != nil { + return err + } + if num == 0 { + fmt.Println("Given domain not found.") + return nil + } + + fmt.Println("Reject Follow request : " + domain) + data, err := redClient.HGetAll("relay:pending:" + domain).Result() + if err != nil { + return err + } + activity := activitypub.Activity{ + []string{"https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"}, + data["activity_id"], + data["actor"], + data["type"], + data["object"], + nil, + nil, + } + + actorDomain, _ := url.Parse(activity.Actor) + resp := activitypub.GenerateActivityResponse(hostname, actorDomain, "Reject", activity) + jsonData, _ := json.Marshal(&resp) + + pushRegistorJob(data["inbox_url"], jsonData) + redClient.Del("relay:pending:" + domain) + return nil + } else { + fmt.Println("No domain given.") + return nil + } +} diff --git a/decode_test.go b/decode_test.go index 14ba1f2..ba65187 100644 --- a/decode_test.go +++ b/decode_test.go @@ -7,7 +7,9 @@ import ( ) func TestHandleInboxNoSignature(t *testing.T) { - s := httptest.NewServer(http.HandlerFunc(handleInbox)) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handleInbox(w, r, decodeActivity) + })) defer s.Close() req, _ := http.NewRequest("POST", s.URL, nil) @@ -22,7 +24,9 @@ func TestHandleInboxNoSignature(t *testing.T) { } func TestHandleInboxInvalidMethod(t *testing.T) { - s := httptest.NewServer(http.HandlerFunc(handleInbox)) + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handleInbox(w, r, decodeActivity) + })) defer s.Close() req, _ := http.NewRequest("GET", s.URL, nil) diff --git a/handle.go b/handle.go index 069cc4f..1103709 100644 --- a/handle.go +++ b/handle.go @@ -152,20 +152,21 @@ func suitableRelay(activity *activitypub.Activity, actor *activitypub.Actor) boo if limited { return false } - if relConfig.blockService && actor.Type == "Service" { + if relConfig.BlockService && actor.Type == "Service" { return false } return true } -func handleInbox(w http.ResponseWriter, r *http.Request) { +func handleInbox(w http.ResponseWriter, r *http.Request, activityDecoder func(*http.Request) (*activitypub.Activity, *activitypub.Actor, []byte, error)) { switch r.Method { case "POST": - activity, actor, body, err := decodeActivity(r) + activity, actor, body, err := activityDecoder(r) if err != nil { w.WriteHeader(400) w.Write(nil) } else { + domain, _ := url.Parse(activity.Actor) switch activity.Type { case "Follow": err = followAcceptable(activity, actor) @@ -173,29 +174,39 @@ func handleInbox(w http.ResponseWriter, r *http.Request) { w.WriteHeader(400) w.Write([]byte(err.Error())) } else { - domain, _ := url.Parse(activity.Actor) - var responseType string if suitableFollow(activity, actor) { - responseType = "Accept" - redClient.HSet("relay:subscription:"+domain.Host, "inbox_url", actor.Endpoints.SharedInbox) + if relConfig.ManuallyAccept { + redClient.HMSet("relay:pending:"+domain.Host, map[string]interface{}{ + "inbox_url": actor.Endpoints.SharedInbox, + "activity_id": activity.ID, + "type": "Follow", + "actor": actor.ID, + "object": activity.Object.(string), + }) + fmt.Println("Pending Follow Request : ", activity.Actor) + } else { + resp := activitypub.GenerateActivityResponse(hostname, domain, "Accept", *activity) + jsonData, _ := json.Marshal(&resp) + go pushRegistorJob(actor.Inbox, jsonData) + redClient.HSet("relay:subscription:"+domain.Host, "inbox_url", actor.Endpoints.SharedInbox) + fmt.Println("Accept Follow Request : ", activity.Actor) + } } else { - responseType = "Reject" + resp := activitypub.GenerateActivityResponse(hostname, domain, "Reject", *activity) + jsonData, _ := json.Marshal(&resp) + go pushRegistorJob(actor.Inbox, jsonData) + fmt.Println("Reject Follow Request : ", activity.Actor) } - resp := activitypub.GenerateActivityResponse(hostname, domain, responseType, *activity) - jsonData, _ := json.Marshal(&resp) - go pushRegistorJob(actor.Inbox, jsonData) - fmt.Println(responseType+" Follow Request : ", activity.Actor) w.WriteHeader(202) w.Write(nil) } case "Undo": nestedActivity, _ := activitypub.DescribeNestedActivity(activity.Object) if nestedActivity.Type == "Follow" && nestedActivity.Actor == activity.Actor { - domain, _ := url.Parse(activity.Actor) redClient.Del("relay:subscription:" + domain.Host) - fmt.Println("Accept Unfollow Request : ", activity.Actor) + w.WriteHeader(202) w.Write(nil) } else { @@ -206,8 +217,8 @@ func handleInbox(w http.ResponseWriter, r *http.Request) { } else { domain, _ := url.Parse(activity.Actor) go pushRelayJob(domain.Host, body) - fmt.Println("Accept Relay Status : ", activity.Actor) + w.WriteHeader(202) w.Write(nil) } @@ -219,9 +230,7 @@ func handleInbox(w http.ResponseWriter, r *http.Request) { w.Write([]byte(err.Error())) } else { if suitableRelay(activity, actor) { - domain, _ := url.Parse(activity.Actor) go pushRelayJob(domain.Host, body) - fmt.Println("Accept Relay Status : ", activity.Actor) } else { fmt.Println("Skipping Relay Status : ", activity.Actor) diff --git a/main.go b/main.go index 3893f8a..49fa837 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "github.com/go-redis/redis" "github.com/yukimochi/Activity-Relay/ActivityPub" "github.com/yukimochi/Activity-Relay/KeyLoader" + "github.com/yukimochi/Activity-Relay/RelayConf" ) // Actor : Relay's Actor @@ -20,26 +21,11 @@ var Actor activitypub.Actor // WebfingerResource : Relay's Webfinger resource var WebfingerResource activitypub.WebfingerResource -type relayConfig struct { - blockService bool -} - var hostname *url.URL var hostkey *rsa.PrivateKey var redClient *redis.Client var macServer *machinery.Server -var relConfig relayConfig - -func loadConfig() relayConfig { - blockService, err := redClient.HGet("relay:config", "block_service").Result() - if err != nil { - redClient.HSet("relay:config", "block_service", 0) - blockService = "0" - } - return relayConfig{ - blockService: blockService == "1", - } -} +var relConfig relayconf.RelayConfig func main() { pemPath := os.Getenv("ACTOR_PEM") @@ -88,11 +74,13 @@ func main() { WebfingerResource = activitypub.GenerateWebfingerResource(hostname, &Actor) // Load Config - relConfig = loadConfig() + relConfig = relayconf.LoadConfig(redClient) http.HandleFunc("/.well-known/webfinger", handleWebfinger) http.HandleFunc("/actor", handleActor) - http.HandleFunc("/inbox", handleInbox) + http.HandleFunc("/inbox", func(w http.ResponseWriter, r *http.Request) { + handleInbox(w, r, decodeActivity) + }) fmt.Println("Welcome to YUKIMOCHI Activity-Relay [Server]\n - Configrations") fmt.Println("RELAY DOMAIN : ", relayDomain)