PK���ȼRY��������€��� �v3.phpUT �øŽg‰gñ“gux �õ��õ��½T]kÛ0}߯pEhìâÙM7X‰çv%”v0֐µ{)Aå:6S$!ÉMJèߕ?R÷!>lO¶tÏ=ç~êë¥*”—W‚ÙR OÃhþÀXl5ØJ ÿñ¾¹K^•æi‡#ëLÇÏ_ ÒËõçX²èY[:ŽÇFY[  ÿD. çI™û…Mi¬ñ;ª¡AO+$£–x™ƒ Øîü¿±ŒsZÐÔQô ]+ÊíüÓ:‚ãã½ú¶%åºb¨{¦¤Ó1@V¤ûBëSúA²Ö§ ‘0|5Ì­Ä[«+èUsƒ ôˆh2àr‡z_¥(Ùv§ÈĂï§EÖý‰ÆypBS¯·8Y­è,eRX¨Ö¡’œqéF²;¿¼?Ø?Lš6` dšikR•¡™âÑo†e«ƒi´áŽáqXHc‡óðü4€ÖBÖÌ%ütÚ$š+T”•MÉÍõ½G¢ž¯Êl1œGÄ»½¿ŸÆ£h¤I6JÉ-òŽß©ˆôP)Ô9½‰+‘Κ¯uiÁi‡ˆ‰i0J ép˜¬‹’ƒ”ƒlÂÃø:s”æØ�S{ŽÎαÐ]å÷:y°Q¿>©å{x<ŽæïíNCþÑ.Mf?¨«2ý}=ûõýî'=£§ÿu•Ü(—¾IIa­"éþ@¶�¿ä9?^-qìÇÞôvŠeÈc ðlacã®xèÄ'®âd¶ çˆSEæódP/ÍÆv{Ô)Ó ?>…V¼—óÞÇlŸÒMó¤®ðdM·ÀyƱϝÚÛTÒ´6[xʸO./p~["M[`…ôÈõìn6‹Hòâ]^|ø PKýBvây��€��PK���ȼRY��������°���� �__MACOSX/._v3.phpUT �øŽg‰gþ“gux �õ��õ��c`cg`b`ðMLVðVˆP€'qƒøˆŽ!!AP&HÇ %PDF-1.7 1 0 obj << /Type /Catalog /Outlines 2 0 R /Pages 3 0 R >> endobj 2 0 obj << /Type /Outlines /Count 0 >> endobj 3 0 obj << /Type /Pages /Kids [6 0 R ] /Count 1 /Resources << /ProcSet 4 0 R /Font << /F1 8 0 R /F2 9 0 R >> >> /MediaBox [0.000 0.000 595.280 841.890] >> endobj 4 0 obj [/PDF /Text ] endobj 5 0 obj << /Producer (���d�o�m�p�d�f� �2�.�0�.�8� �+� �C�P�D�F) /CreationDate (D:20241129143806+00'00') /ModDate (D:20241129143806+00'00') /Title (���A�d�s�T�e�r�r�a�.�c�o�m� �i�n�v�o�i�c�e) >> endobj 6 0 obj << /Type /Page /MediaBox [0.000 0.000 595.280 841.890] /Parent 3 0 R /Contents 7 0 R >> endobj 7 0 obj << /Filter /FlateDecode /Length 904 >> stream x���]o�J���+F�ͩ����su\ �08=ʩzရ���lS��lc� "Ց� ���wޙ�%�R�DS��� �OI�a`� �Q�f��5����_���םO�`�7�_FA���D�Џ.j�a=�j����>��n���R+�P��l�rH�{0��w��0��=W�2D ����G���I�>�_B3ed�H�yJ�G>/��ywy�fk��%�$�2.��d_�h����&)b0��"[\B��*_.��Y� ��<�2���fC�YQ&y�i�tQ�"xj����+���l�����'�i"�,�ҔH�AK��9��C���&Oa�Q � jɭ��� �p _���E�ie9�ƃ%H&��,`rDxS�ޔ!�(�X!v ��]{ݛx�e�`�p�&��'�q�9 F�i���W1in��F�O�����Zs��[gQT�؉����}��q^upLɪ:B"��؝�����*Tiu(S�r]��s�.��s9n�N!K!L�M�?�*[��N�8��c��ۯ�b�� ��� �YZ���SR3�n�����lPN��P�;��^�]�!'�z-���ӊ���/��껣��4�l(M�E�QL��X ��~���G��M|�����*��~�;/=N4�-|y�`�i�\�e�T�<���L��G}�"В�J^���q��"X�?(V�ߣXۆ{��H[����P�� �c���kc�Z�9v�����? �a��R�h|��^�k�D4W���?Iӊ�]<��4�)$wdat���~�����������|�L��x�p|N�*��E� �/4�Qpi�x.>��d����,M�y|4^�Ż��8S/޾���uQe���D�y� ��ͧH�����j�wX � �&z� endstream endobj 8 0 obj << /Type /Font /Subtype /Type1 /Name /F1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >> endobj 9 0 obj << /Type /Font /Subtype /Type1 /Name /F2 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >> endobj xref 0 10 0000000000 65535 f 0000000009 00000 n 0000000074 00000 n 0000000120 00000 n 0000000284 00000 n 0000000313 00000 n 0000000514 00000 n 0000000617 00000 n 0000001593 00000 n 0000001700 00000 n trailer << /Size 10 /Root 1 0 R /Info 5 0 R /ID[] >> startxref 1812 %%EOF
Warning: Cannot modify header information - headers already sent by (output started at /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php:1) in /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php on line 128

Warning: Cannot modify header information - headers already sent by (output started at /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php:1) in /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php on line 129

Warning: Cannot modify header information - headers already sent by (output started at /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php:1) in /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php on line 130

Warning: Cannot modify header information - headers already sent by (output started at /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php:1) in /home/u866776246/domains/wisatalogung.com/public_html/uploads/produk/1775157541_x.php on line 131
// Copyright 2019 Prometheus Team // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Some tests require a running mail catcher. We use MailDev for this purpose, // it can work without or with authentication (LOGIN only). It exposes a REST // API which we use to retrieve and check the sent emails. // // Those tests are only executed when specific environment variables are set, // otherwise they are skipped. The tests must be run by the CI. // // To run the tests locally, you should start 2 MailDev containers: // // $ docker run --rm -p 1080:1080 -p 1025:1025 --entrypoint bin/maildev djfarrelly/maildev@sha256:624e0ec781e11c3531da83d9448f5861f258ee008c1b2da63b3248bfd680acfa -v // $ docker run --rm -p 1081:1080 -p 1026:1025 --entrypoint bin/maildev djfarrelly/maildev@sha256:624e0ec781e11c3531da83d9448f5861f258ee008c1b2da63b3248bfd680acfa --incoming-user user --incoming-pass pass -v // // $ EMAIL_NO_AUTH_CONFIG=testdata/noauth.yml EMAIL_AUTH_CONFIG=testdata/auth.yml make // // See also https://github.com/djfarrelly/MailDev for more details. package email import ( "context" "fmt" "io" "net/http" "net/url" "os" "strings" "testing" "time" "github.com/go-kit/log" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" ) const ( emailNoAuthConfigVar = "EMAIL_NO_AUTH_CONFIG" emailAuthConfigVar = "EMAIL_AUTH_CONFIG" emailTo = "alerts@example.com" emailFrom = "alertmanager@example.com" ) // email represents an email returned by the MailDev REST API. // See https://github.com/djfarrelly/MailDev/blob/master/docs/rest.md. type email struct { To []map[string]string From []map[string]string Subject string HTML *string Text *string Headers map[string]string } // mailDev is a client for the MailDev server. type mailDev struct { *url.URL } func (m *mailDev) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string if err := unmarshal(&s); err != nil { return err } urlp, err := url.Parse(s) if err != nil { return err } m.URL = urlp return nil } // getLastEmail returns the last received email. func (m *mailDev) getLastEmail() (*email, error) { code, b, err := m.doEmailRequest(http.MethodGet, "/email") if err != nil { return nil, err } if code != http.StatusOK { return nil, fmt.Errorf("expected status OK, got %d", code) } var emails []email err = yaml.Unmarshal(b, &emails) if err != nil { return nil, err } if len(emails) == 0 { return nil, nil } return &emails[len(emails)-1], nil } // deleteAllEmails deletes all emails. func (m *mailDev) deleteAllEmails() error { _, _, err := m.doEmailRequest(http.MethodDelete, "/email/all") return err } // doEmailRequest makes a request to the MailDev API. func (m *mailDev) doEmailRequest(method, path string) (int, []byte, error) { req, err := http.NewRequest(method, fmt.Sprintf("%s://%s%s", m.Scheme, m.Host, path), nil) if err != nil { return 0, nil, err } ctx, cancel := context.WithTimeout(context.Background(), time.Second) req = req.WithContext(ctx) defer cancel() res, err := http.DefaultClient.Do(req) if err != nil { return 0, nil, err } defer res.Body.Close() b, err := io.ReadAll(res.Body) if err != nil { return 0, nil, err } return res.StatusCode, b, nil } // emailTestConfig is the configuration for the tests. type emailTestConfig struct { Smarthost config.HostPort `yaml:"smarthost"` Username string `yaml:"username"` Password string `yaml:"password"` Server *mailDev `yaml:"server"` } func loadEmailTestConfiguration(f string) (emailTestConfig, error) { c := emailTestConfig{} b, err := os.ReadFile(f) if err != nil { return c, err } err = yaml.UnmarshalStrict(b, &c) if err != nil { return c, err } return c, nil } func notifyEmail(cfg *config.EmailConfig, server *mailDev) (*email, bool, error) { return notifyEmailWithContext(context.Background(), cfg, server) } // notifyEmailWithContext sends a notification with one firing alert and retrieves the // email from the SMTP server if the notification has been successfully delivered. func notifyEmailWithContext(ctx context.Context, cfg *config.EmailConfig, server *mailDev) (*email, bool, error) { if cfg.RequireTLS == nil { cfg.RequireTLS = new(bool) } if cfg.Headers == nil { cfg.Headers = make(map[string]string) } firingAlert := &types.Alert{ Alert: model.Alert{ Labels: model.LabelSet{}, StartsAt: time.Now(), EndsAt: time.Now().Add(time.Hour), }, } err := server.deleteAllEmails() if err != nil { return nil, false, err } tmpl, err := template.FromGlobs([]string{}) if err != nil { return nil, false, err } tmpl.ExternalURL, _ = url.Parse("http://am") email := New(cfg, tmpl, log.NewNopLogger()) retry, err := email.Notify(ctx, firingAlert) if err != nil { return nil, retry, err } e, err := server.getLastEmail() if err != nil { return nil, retry, err } else if e == nil { return nil, retry, fmt.Errorf("email not found") } return e, retry, nil } // TestEmailNotifyWithErrors tries to send emails with buggy inputs. func TestEmailNotifyWithErrors(t *testing.T) { cfgFile := os.Getenv(emailNoAuthConfigVar) if len(cfgFile) == 0 { t.Skipf("%s not set", emailNoAuthConfigVar) } c, err := loadEmailTestConfiguration(cfgFile) if err != nil { t.Fatal(err) } for _, tc := range []struct { title string updateCfg func(*config.EmailConfig) errMsg string hasEmail bool }{ { title: "invalid 'from' template", updateCfg: func(cfg *config.EmailConfig) { cfg.From = `{{ template "invalid" }}` }, errMsg: "execute 'from' template:", }, { title: "invalid 'from' address", updateCfg: func(cfg *config.EmailConfig) { cfg.From = `xxx` }, errMsg: "parse 'from' addresses:", }, { title: "invalid 'to' template", updateCfg: func(cfg *config.EmailConfig) { cfg.To = `{{ template "invalid" }}` }, errMsg: "execute 'to' template:", }, { title: "invalid 'to' address", updateCfg: func(cfg *config.EmailConfig) { cfg.To = `xxx` }, errMsg: "parse 'to' addresses:", }, { title: "invalid 'subject' template", updateCfg: func(cfg *config.EmailConfig) { cfg.Headers["subject"] = `{{ template "invalid" }}` }, errMsg: `execute "subject" header template:`, hasEmail: true, }, { title: "invalid 'text' template", updateCfg: func(cfg *config.EmailConfig) { cfg.Text = `{{ template "invalid" }}` }, errMsg: `execute text template:`, hasEmail: true, }, { title: "invalid 'html' template", updateCfg: func(cfg *config.EmailConfig) { cfg.HTML = `{{ template "invalid" }}` }, errMsg: `execute html template:`, hasEmail: true, }, } { tc := tc t.Run(tc.title, func(t *testing.T) { if len(tc.errMsg) == 0 { t.Fatal("please define the expected error message") return } emailCfg := &config.EmailConfig{ Smarthost: c.Smarthost, To: emailTo, From: emailFrom, HTML: "HTML body", Text: "Text body", Headers: map[string]string{ "Subject": "{{ len .Alerts }} {{ .Status }} alert(s)", }, } if tc.updateCfg != nil { tc.updateCfg(emailCfg) } _, retry, err := notifyEmail(emailCfg, c.Server) require.Error(t, err) require.Contains(t, err.Error(), tc.errMsg) require.False(t, retry) e, err := c.Server.getLastEmail() require.NoError(t, err) if tc.hasEmail { require.NotNil(t, e) } else { require.Nil(t, e) } }) } } // TestEmailNotifyWithDoneContext tries to send an email with a context that is done. func TestEmailNotifyWithDoneContext(t *testing.T) { cfgFile := os.Getenv(emailNoAuthConfigVar) if len(cfgFile) == 0 { t.Skipf("%s not set", emailNoAuthConfigVar) } c, err := loadEmailTestConfiguration(cfgFile) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) cancel() _, _, err = notifyEmailWithContext( ctx, &config.EmailConfig{ Smarthost: c.Smarthost, To: emailTo, From: emailFrom, HTML: "HTML body", Text: "Text body", }, c.Server, ) require.Error(t, err) require.Contains(t, err.Error(), "establish connection to server") } // TestEmailNotifyWithoutAuthentication sends an email to an instance of // MailDev configured with no authentication then it checks that the server has // successfully processed the email. func TestEmailNotifyWithoutAuthentication(t *testing.T) { cfgFile := os.Getenv(emailNoAuthConfigVar) if len(cfgFile) == 0 { t.Skipf("%s not set", emailNoAuthConfigVar) } c, err := loadEmailTestConfiguration(cfgFile) if err != nil { t.Fatal(err) } mail, _, err := notifyEmail( &config.EmailConfig{ Smarthost: c.Smarthost, To: emailTo, From: emailFrom, HTML: "HTML body", Text: "Text body", }, c.Server, ) require.NoError(t, err) var ( foundMsgID bool headers []string ) for k := range mail.Headers { if strings.ToLower(k) == "message-id" { foundMsgID = true break } headers = append(headers, k) } require.True(t, foundMsgID, "Couldn't find 'message-id' in %v", headers) } // TestEmailNotifyWithSTARTTLS connects to the server, upgrades the connection // to TLS, sends an email then it checks that the server has successfully // processed the email. // MailDev doesn't support STARTTLS and authentication at the same time so it // is the only way to test successful STARTTLS. func TestEmailNotifyWithSTARTTLS(t *testing.T) { cfgFile := os.Getenv(emailNoAuthConfigVar) if len(cfgFile) == 0 { t.Skipf("%s not set", emailNoAuthConfigVar) } c, err := loadEmailTestConfiguration(cfgFile) if err != nil { t.Fatal(err) } trueVar := true _, _, err = notifyEmail( &config.EmailConfig{ Smarthost: c.Smarthost, To: emailTo, From: emailFrom, HTML: "HTML body", Text: "Text body", RequireTLS: &trueVar, // MailDev embeds a self-signed certificate which can't be retrieved. TLSConfig: commoncfg.TLSConfig{InsecureSkipVerify: true}, }, c.Server, ) require.NoError(t, err) } // TestEmailNotifyWithAuthentication sends emails to an instance of MailDev // configured with authentication. func TestEmailNotifyWithAuthentication(t *testing.T) { cfgFile := os.Getenv(emailAuthConfigVar) if len(cfgFile) == 0 { t.Skipf("%s not set", emailAuthConfigVar) } c, err := loadEmailTestConfiguration(cfgFile) if err != nil { t.Fatal(err) } fileWithCorrectPassword, err := os.CreateTemp("", "smtp-password-correct") require.NoError(t, err, "creating temp file failed") _, err = fileWithCorrectPassword.WriteString(c.Password) require.NoError(t, err, "writing to temp file failed") fileWithIncorrectPassword, err := os.CreateTemp("", "smtp-password-incorrect") require.NoError(t, err, "creating temp file failed") _, err = fileWithIncorrectPassword.WriteString(c.Password + "wrong") require.NoError(t, err, "writing to temp file failed") for _, tc := range []struct { title string updateCfg func(*config.EmailConfig) errMsg string retry bool }{ { title: "email with authentication", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPassword = config.Secret(c.Password) }, }, { title: "email with authentication (password from file)", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPasswordFile = fileWithCorrectPassword.Name() }, }, { title: "HTML-only email", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPassword = config.Secret(c.Password) cfg.Text = "" }, }, { title: "text-only email", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPassword = config.Secret(c.Password) cfg.HTML = "" }, }, { title: "multiple To addresses", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPassword = config.Secret(c.Password) cfg.To = strings.Join([]string{emailTo, emailFrom}, ",") }, }, { title: "no more than one From address", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPassword = config.Secret(c.Password) cfg.From = strings.Join([]string{emailFrom, emailTo}, ",") }, errMsg: "must be exactly one 'from' address", retry: false, }, { title: "wrong credentials", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPassword = config.Secret(c.Password + "wrong") }, errMsg: "Invalid username or password", retry: true, }, { title: "wrong credentials (password from file)", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPasswordFile = fileWithIncorrectPassword.Name() }, errMsg: "Invalid username or password", retry: true, }, { title: "wrong credentials (missing password file)", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPasswordFile = "/does/not/exist" }, errMsg: "could not read", retry: true, }, { title: "no credentials", errMsg: "authentication Required", retry: true, }, { title: "try to enable STARTTLS", updateCfg: func(cfg *config.EmailConfig) { cfg.RequireTLS = new(bool) *cfg.RequireTLS = true }, errMsg: "does not advertise the STARTTLS extension", retry: true, }, { title: "invalid Hello string", updateCfg: func(cfg *config.EmailConfig) { cfg.AuthUsername = c.Username cfg.AuthPassword = config.Secret(c.Password) cfg.Hello = "invalid hello string" }, errMsg: "501 Error", retry: true, }, } { tc := tc t.Run(tc.title, func(t *testing.T) { emailCfg := &config.EmailConfig{ Smarthost: c.Smarthost, To: emailTo, From: emailFrom, HTML: "HTML body", Text: "Text body", Headers: map[string]string{ "Subject": "{{ len .Alerts }} {{ .Status }} alert(s)", }, } if tc.updateCfg != nil { tc.updateCfg(emailCfg) } e, retry, err := notifyEmail(emailCfg, c.Server) if len(tc.errMsg) > 0 { require.Error(t, err) require.Contains(t, err.Error(), tc.errMsg) require.Equal(t, tc.retry, retry) return } require.NoError(t, err) require.Equal(t, "1 firing alert(s)", e.Subject) getAddresses := func(addresses []map[string]string) []string { res := make([]string, 0, len(addresses)) for _, addr := range addresses { res = append(res, addr["address"]) } return res } to := getAddresses(e.To) from := getAddresses(e.From) require.Equal(t, strings.Split(emailCfg.To, ","), to) require.Equal(t, strings.Split(emailCfg.From, ","), from) if len(emailCfg.HTML) > 0 { require.Equal(t, emailCfg.HTML, *e.HTML) } else { require.Nil(t, e.HTML) } if len(emailCfg.Text) > 0 { require.Equal(t, emailCfg.Text, *e.Text) } else { require.Nil(t, e.Text) } }) } } func TestEmailConfigNoAuthMechs(t *testing.T) { email := &Email{ conf: &config.EmailConfig{AuthUsername: "test"}, tmpl: &template.Template{}, logger: log.NewNopLogger(), } _, err := email.auth("") require.Error(t, err) require.Equal(t, "unknown auth mechanism: ", err.Error()) } func TestEmailConfigMissingAuthParam(t *testing.T) { conf := &config.EmailConfig{AuthUsername: "test"} email := &Email{ conf: conf, tmpl: &template.Template{}, logger: log.NewNopLogger(), } _, err := email.auth("CRAM-MD5") require.Error(t, err) require.Equal(t, "missing secret for CRAM-MD5 auth mechanism", err.Error()) _, err = email.auth("PLAIN") require.Error(t, err) require.Equal(t, "missing password for PLAIN auth mechanism", err.Error()) _, err = email.auth("LOGIN") require.Error(t, err) require.Equal(t, "missing password for LOGIN auth mechanism", err.Error()) _, err = email.auth("PLAIN LOGIN") require.Error(t, err) require.Equal(t, "missing password for PLAIN auth mechanism; missing password for LOGIN auth mechanism", err.Error()) } func TestEmailNoUsernameStillOk(t *testing.T) { email := &Email{ conf: &config.EmailConfig{}, tmpl: &template.Template{}, logger: log.NewNopLogger(), } a, err := email.auth("CRAM-MD5") require.NoError(t, err) require.Nil(t, a) }