Support new Apple Token Based Authentication (JWT) (#300)
* Support new Apple Token Based Authentication (JWT) Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com> * fix testing Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
parent
461a57ec9a
commit
c06e819e08
|
@ -113,6 +113,8 @@ ios:
|
||||||
password: "" # certificate password, default as empty string.
|
password: "" # certificate password, default as empty string.
|
||||||
production: false
|
production: false
|
||||||
max_retry: 0 # resend fail notification, default value zero is disabled
|
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:
|
log:
|
||||||
format: "string" # string or json
|
format: "string" # string or json
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE
|
||||||
|
ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI
|
||||||
|
tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfZZZ
|
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE
|
||||||
|
ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI
|
||||||
|
tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfeQl
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -56,6 +56,8 @@ ios:
|
||||||
password: "" # certificate password, default as empty string.
|
password: "" # certificate password, default as empty string.
|
||||||
production: false
|
production: false
|
||||||
max_retry: 0 # resend fail notification, default value zero is disabled
|
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:
|
log:
|
||||||
format: "string" # string or json
|
format: "string" # string or json
|
||||||
|
@ -140,6 +142,8 @@ type SectionIos struct {
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
Production bool `yaml:"production"`
|
Production bool `yaml:"production"`
|
||||||
MaxRetry int `yaml:"max_retry"`
|
MaxRetry int `yaml:"max_retry"`
|
||||||
|
KeyID string `yaml:"key_id"`
|
||||||
|
TeamID string `yaml:"team_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SectionLog is sub section of config.
|
// 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.Password = viper.GetString("ios.password")
|
||||||
conf.Ios.Production = viper.GetBool("ios.production")
|
conf.Ios.Production = viper.GetBool("ios.production")
|
||||||
conf.Ios.MaxRetry = viper.GetInt("ios.max_retry")
|
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
|
// log
|
||||||
conf.Log.Format = viper.GetString("log.format")
|
conf.Log.Format = viper.GetString("log.format")
|
||||||
|
|
|
@ -43,6 +43,8 @@ ios:
|
||||||
password: "" # certificate password, default as empty string.
|
password: "" # certificate password, default as empty string.
|
||||||
production: false
|
production: false
|
||||||
max_retry: 0 # resend fail notification, default value zero is disabled
|
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:
|
log:
|
||||||
format: "string" # string or json
|
format: "string" # string or json
|
||||||
|
|
|
@ -76,6 +76,8 @@ func (suite *ConfigTestSuite) TestValidateConfDefault() {
|
||||||
assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.Password)
|
assert.Equal(suite.T(), "", suite.ConfGorushDefault.Ios.Password)
|
||||||
assert.Equal(suite.T(), false, suite.ConfGorushDefault.Ios.Production)
|
assert.Equal(suite.T(), false, suite.ConfGorushDefault.Ios.Production)
|
||||||
assert.Equal(suite.T(), 0, suite.ConfGorushDefault.Ios.MaxRetry)
|
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
|
// log
|
||||||
assert.Equal(suite.T(), "string", suite.ConfGorushDefault.Log.Format)
|
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(), "", suite.ConfGorush.Ios.Password)
|
||||||
assert.Equal(suite.T(), false, suite.ConfGorush.Ios.Production)
|
assert.Equal(suite.T(), false, suite.ConfGorush.Ios.Production)
|
||||||
assert.Equal(suite.T(), 0, suite.ConfGorush.Ios.MaxRetry)
|
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
|
// log
|
||||||
assert.Equal(suite.T(), "string", suite.ConfGorush.Log.Format)
|
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_CORE_PORT", "9001")
|
||||||
os.Setenv("GORUSH_GRPC_ENABLED", "true")
|
os.Setenv("GORUSH_GRPC_ENABLED", "true")
|
||||||
os.Setenv("GORUSH_CORE_MAX_NOTIFICATION", "200")
|
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")
|
ConfGorush, err := LoadConf("config.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to load config.yml from file")
|
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, "9001", ConfGorush.Core.Port)
|
||||||
assert.Equal(t, int64(200), ConfGorush.Core.MaxNotification)
|
assert.Equal(t, int64(200), ConfGorush.Core.MaxNotification)
|
||||||
assert.True(t, ConfGorush.GRPC.Enabled)
|
assert.True(t, ConfGorush.GRPC.Enabled)
|
||||||
|
assert.Equal(t, "ABC123DEFG", ConfGorush.Ios.KeyID)
|
||||||
|
assert.Equal(t, "DEF123GHIJ", ConfGorush.Ios.TeamID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package gorush
|
package gorush
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"github.com/appleboy/gorush/config"
|
"github.com/appleboy/gorush/config"
|
||||||
"github.com/appleboy/gorush/storage"
|
"github.com/appleboy/gorush/storage"
|
||||||
|
|
||||||
"github.com/appleboy/go-fcm"
|
"github.com/appleboy/go-fcm"
|
||||||
apns "github.com/sideshow/apns2"
|
"github.com/sideshow/apns2"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,10 +14,8 @@ var (
|
||||||
PushConf config.ConfYaml
|
PushConf config.ConfYaml
|
||||||
// QueueNotification is chan type
|
// QueueNotification is chan type
|
||||||
QueueNotification chan PushNotification
|
QueueNotification chan PushNotification
|
||||||
// CertificatePemIos is ios certificate file
|
|
||||||
CertificatePemIos tls.Certificate
|
|
||||||
// ApnsClient is apns client
|
// ApnsClient is apns client
|
||||||
ApnsClient *apns.Client
|
ApnsClient *apns2.Client
|
||||||
// FCMClient is apns client
|
// FCMClient is apns client
|
||||||
FCMClient *fcm.Client
|
FCMClient *fcm.Client
|
||||||
// LogAccess is log server request log
|
// LogAccess is log server request log
|
||||||
|
|
|
@ -1,26 +1,33 @@
|
||||||
package gorush
|
package gorush
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
apns "github.com/sideshow/apns2"
|
"github.com/sideshow/apns2"
|
||||||
"github.com/sideshow/apns2/certificate"
|
"github.com/sideshow/apns2/certificate"
|
||||||
"github.com/sideshow/apns2/payload"
|
"github.com/sideshow/apns2/payload"
|
||||||
|
"github.com/sideshow/apns2/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitAPNSClient use for initialize APNs Client.
|
// InitAPNSClient use for initialize APNs Client.
|
||||||
func InitAPNSClient() error {
|
func InitAPNSClient() error {
|
||||||
if PushConf.Ios.Enabled {
|
if PushConf.Ios.Enabled {
|
||||||
var err error
|
var err error
|
||||||
|
var authKey *ecdsa.PrivateKey
|
||||||
|
var certificateKey tls.Certificate
|
||||||
ext := filepath.Ext(PushConf.Ios.KeyPath)
|
ext := filepath.Ext(PushConf.Ios.KeyPath)
|
||||||
|
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".p12":
|
case ".p12":
|
||||||
CertificatePemIos, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password)
|
certificateKey, err = certificate.FromP12File(PushConf.Ios.KeyPath, PushConf.Ios.Password)
|
||||||
case ".pem":
|
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:
|
default:
|
||||||
err = errors.New("wrong certificate key extension")
|
err = errors.New("wrong certificate key extension")
|
||||||
}
|
}
|
||||||
|
@ -31,10 +38,25 @@ func InitAPNSClient() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if PushConf.Ios.Production {
|
||||||
ApnsClient = apns.NewClient(CertificatePemIos).Production()
|
ApnsClient = apns2.NewTokenClient(token).Production()
|
||||||
} else {
|
} else {
|
||||||
ApnsClient = apns.NewClient(CertificatePemIos).Development()
|
ApnsClient = apns2.NewTokenClient(token).Development()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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.
|
// GetIOSNotification use for define iOS notification.
|
||||||
// The iOS Notification Payload
|
// The iOS Notification Payload
|
||||||
// ref: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html#//apple_ref/doc/uid/TP40008194-CH17-SW1
|
// 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 {
|
func GetIOSNotification(req PushNotification) *apns2.Notification {
|
||||||
notification := &apns.Notification{
|
notification := &apns2.Notification{
|
||||||
ApnsID: req.ApnsID,
|
ApnsID: req.ApnsID,
|
||||||
Topic: req.Topic,
|
Topic: req.Topic,
|
||||||
}
|
}
|
||||||
|
@ -112,7 +134,7 @@ func GetIOSNotification(req PushNotification) *apns.Notification {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Priority) > 0 && req.Priority == "normal" {
|
if len(req.Priority) > 0 && req.Priority == "normal" {
|
||||||
notification.Priority = apns.PriorityLow
|
notification.Priority = apns2.PriorityLow
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := payload.NewPayload()
|
payload := payload.NewPayload()
|
||||||
|
|
|
@ -412,6 +412,30 @@ func TestAPNSClientProdHost(t *testing.T) {
|
||||||
assert.Equal(t, apns2.HostProduction, ApnsClient.Host)
|
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) {
|
func TestPushToIOS(t *testing.T) {
|
||||||
PushConf, _ = config.LoadConf("")
|
PushConf, _ = config.LoadConf("")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue