From c06e819e08f6a39d647305eaf08579b11ce1708a Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Wed, 25 Oct 2017 03:49:23 -0500 Subject: [PATCH] Support new Apple Token Based Authentication (JWT) (#300) * Support new Apple Token Based Authentication (JWT) Signed-off-by: Bo-Yi Wu * fix testing Signed-off-by: Bo-Yi Wu --- README.md | 2 ++ certificate/authkey-invalid.p8 | 3 +++ certificate/authkey-valid.p8 | 5 ++++ config/config.go | 6 +++++ config/config.yml | 2 ++ config/config_test.go | 8 +++++++ gorush/global.go | 8 ++----- gorush/notification_apns.go | 40 +++++++++++++++++++++++++------- gorush/notification_apns_test.go | 24 +++++++++++++++++++ 9 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 certificate/authkey-invalid.p8 create mode 100644 certificate/authkey-valid.p8 diff --git a/README.md b/README.md index cd461e8..9891cc7 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ ios: password: "" # certificate password, default as empty string. production: false max_retry: 0 # resend fail notification, default value zero is disabled + key_id: "" # KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) + team_id: "" # TeamID from developer account (View Account -> Membership) log: format: "string" # string or json diff --git a/certificate/authkey-invalid.p8 b/certificate/authkey-invalid.p8 new file mode 100644 index 0000000..74a2d70 --- /dev/null +++ b/certificate/authkey-invalid.p8 @@ -0,0 +1,3 @@ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE +ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI +tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfZZZ diff --git a/certificate/authkey-valid.p8 b/certificate/authkey-valid.p8 new file mode 100644 index 0000000..62c30f5 --- /dev/null +++ b/certificate/authkey-valid.p8 @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE +ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI +tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfeQl +-----END PRIVATE KEY----- diff --git a/config/config.go b/config/config.go index ddc969b..b5d11bc 100644 --- a/config/config.go +++ b/config/config.go @@ -56,6 +56,8 @@ ios: password: "" # certificate password, default as empty string. production: false max_retry: 0 # resend fail notification, default value zero is disabled + key_id: "" # KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) + team_id: "" # TeamID from developer account (View Account -> Membership) log: format: "string" # string or json @@ -140,6 +142,8 @@ type SectionIos struct { Password string `yaml:"password"` Production bool `yaml:"production"` MaxRetry int `yaml:"max_retry"` + KeyID string `yaml:"key_id"` + TeamID string `yaml:"team_id"` } // SectionLog is sub section of config. @@ -269,6 +273,8 @@ func LoadConf(confPath string) (ConfYaml, error) { conf.Ios.Password = viper.GetString("ios.password") conf.Ios.Production = viper.GetBool("ios.production") conf.Ios.MaxRetry = viper.GetInt("ios.max_retry") + conf.Ios.KeyID = viper.GetString("ios.key_id") + conf.Ios.TeamID = viper.GetString("ios.team_id") // log conf.Log.Format = viper.GetString("log.format") diff --git a/config/config.yml b/config/config.yml index 2db1377..4f67b57 100644 --- a/config/config.yml +++ b/config/config.yml @@ -43,6 +43,8 @@ ios: password: "" # certificate password, default as empty string. production: false max_retry: 0 # resend fail notification, default value zero is disabled + key_id: "" # KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) + team_id: "" # TeamID from developer account (View Account -> Membership) log: format: "string" # string or json diff --git a/config/config_test.go b/config/config_test.go index 3615092..0f56add 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -76,6 +76,8 @@ func (suite *ConfigTestSuite) TestValidateConfDefault() { assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.Password) assert.Equal(suite.T(), false, suite.ConfGorushDefault.Ios.Production) assert.Equal(suite.T(), 0, suite.ConfGorushDefault.Ios.MaxRetry) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.KeyID) + assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.TeamID) // log assert.Equal(suite.T(), "string", suite.ConfGorushDefault.Log.Format) @@ -141,6 +143,8 @@ func (suite *ConfigTestSuite) TestValidateConf() { assert.Equal(suite.T(), "", suite.ConfGorush.Ios.Password) assert.Equal(suite.T(), false, suite.ConfGorush.Ios.Production) assert.Equal(suite.T(), 0, suite.ConfGorush.Ios.MaxRetry) + assert.Equal(suite.T(), "", suite.ConfGorush.Ios.KeyID) + assert.Equal(suite.T(), "", suite.ConfGorush.Ios.TeamID) // log assert.Equal(suite.T(), "string", suite.ConfGorush.Log.Format) @@ -174,6 +178,8 @@ func TestLoadConfigFromEnv(t *testing.T) { os.Setenv("GORUSH_CORE_PORT", "9001") os.Setenv("GORUSH_GRPC_ENABLED", "true") os.Setenv("GORUSH_CORE_MAX_NOTIFICATION", "200") + os.Setenv("GORUSH_IOS_KEY_ID", "ABC123DEFG") + os.Setenv("GORUSH_IOS_TEAM_ID", "DEF123GHIJ") ConfGorush, err := LoadConf("config.yml") if err != nil { panic("failed to load config.yml from file") @@ -181,4 +187,6 @@ func TestLoadConfigFromEnv(t *testing.T) { assert.Equal(t, "9001", ConfGorush.Core.Port) assert.Equal(t, int64(200), ConfGorush.Core.MaxNotification) assert.True(t, ConfGorush.GRPC.Enabled) + assert.Equal(t, "ABC123DEFG", ConfGorush.Ios.KeyID) + assert.Equal(t, "DEF123GHIJ", ConfGorush.Ios.TeamID) } diff --git a/gorush/global.go b/gorush/global.go index aadb525..92237aa 100644 --- a/gorush/global.go +++ b/gorush/global.go @@ -1,13 +1,11 @@ package gorush import ( - "crypto/tls" - "github.com/appleboy/gorush/config" "github.com/appleboy/gorush/storage" "github.com/appleboy/go-fcm" - apns "github.com/sideshow/apns2" + "github.com/sideshow/apns2" "github.com/sirupsen/logrus" ) @@ -16,10 +14,8 @@ var ( PushConf config.ConfYaml // QueueNotification is chan type QueueNotification chan PushNotification - // CertificatePemIos is ios certificate file - CertificatePemIos tls.Certificate // ApnsClient is apns client - ApnsClient *apns.Client + ApnsClient *apns2.Client // FCMClient is apns client FCMClient *fcm.Client // LogAccess is log server request log diff --git a/gorush/notification_apns.go b/gorush/notification_apns.go index 8b3720e..4dd9cb7 100644 --- a/gorush/notification_apns.go +++ b/gorush/notification_apns.go @@ -1,26 +1,33 @@ package gorush import ( + "crypto/ecdsa" + "crypto/tls" "errors" "path/filepath" "time" - apns "github.com/sideshow/apns2" + "github.com/sideshow/apns2" "github.com/sideshow/apns2/certificate" "github.com/sideshow/apns2/payload" + "github.com/sideshow/apns2/token" ) // InitAPNSClient use for initialize APNs Client. func InitAPNSClient() error { if PushConf.Ios.Enabled { var err error + var authKey *ecdsa.PrivateKey + var certificateKey tls.Certificate ext := filepath.Ext(PushConf.Ios.KeyPath) switch ext { case ".p12": - CertificatePemIos, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password) + certificateKey, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password) case ".pem": - CertificatePemIos, err = certificate.FromPemFile(PushConf.Ios.KeyPath, PushConf.Ios.Password) + certificateKey, err = certificate.FromPemFile(PushConf.Ios.KeyPath, PushConf.Ios.Password) + case ".p8": + authKey, err = token.AuthKeyFromFile(PushConf.Ios.KeyPath) default: err = errors.New("wrong certificate key extension") } @@ -31,10 +38,25 @@ func InitAPNSClient() error { return err } - if PushConf.Ios.Production { - ApnsClient = apns.NewClient(CertificatePemIos).Production() + if ext == ".p8" && PushConf.Ios.KeyID != "" && PushConf.Ios.TeamID != "" { + token := &token.Token{ + AuthKey: authKey, + // KeyID from developer account (Certificates, Identifiers & Profiles -> Keys) + KeyID: PushConf.Ios.KeyID, + // TeamID from developer account (View Account -> Membership) + TeamID: PushConf.Ios.TeamID, + } + if PushConf.Ios.Production { + ApnsClient = apns2.NewTokenClient(token).Production() + } else { + ApnsClient = apns2.NewTokenClient(token).Development() + } } else { - ApnsClient = apns.NewClient(CertificatePemIos).Development() + if PushConf.Ios.Production { + ApnsClient = apns2.NewClient(certificateKey).Production() + } else { + ApnsClient = apns2.NewClient(certificateKey).Development() + } } } @@ -101,8 +123,8 @@ func iosAlertDictionary(payload *payload.Payload, req PushNotification) *payload // GetIOSNotification use for define iOS notification. // The iOS Notification Payload // ref: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1 -func GetIOSNotification(req PushNotification) *apns.Notification { - notification := &apns.Notification{ +func GetIOSNotification(req PushNotification) *apns2.Notification { + notification := &apns2.Notification{ ApnsID: req.ApnsID, Topic: req.Topic, } @@ -112,7 +134,7 @@ func GetIOSNotification(req PushNotification) *apns.Notification { } if len(req.Priority) > 0 && req.Priority == "normal" { - notification.Priority = apns.PriorityLow + notification.Priority = apns2.PriorityLow } payload := payload.NewPayload() diff --git a/gorush/notification_apns_test.go b/gorush/notification_apns_test.go index 3726887..a62c660 100644 --- a/gorush/notification_apns_test.go +++ b/gorush/notification_apns_test.go @@ -412,6 +412,30 @@ func TestAPNSClientProdHost(t *testing.T) { assert.Equal(t, apns2.HostProduction, ApnsClient.Host) } +func TestAPNSClientInvaildToken(t *testing.T) { + PushConf, _ = config.LoadConf("") + + PushConf.Ios.Enabled = true + PushConf.Ios.KeyPath = "../certificate/authkey-invalid.p8" + err := InitAPNSClient() + assert.Error(t, err) +} + +func TestAPNSClientVaildToken(t *testing.T) { + PushConf, _ = config.LoadConf("") + + PushConf.Ios.Enabled = true + PushConf.Ios.KeyPath = "../certificate/authkey-valid.p8" + err := InitAPNSClient() + assert.NoError(t, err) + assert.Equal(t, apns2.HostDevelopment, ApnsClient.Host) + + PushConf.Ios.Production = true + err = InitAPNSClient() + assert.NoError(t, err) + assert.Equal(t, apns2.HostProduction, ApnsClient.Host) +} + func TestPushToIOS(t *testing.T) { PushConf, _ = config.LoadConf("")