Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package semaphore for openSUSE:Factory 
checked in at 2026-07-14 13:51:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/semaphore (Old)
 and      /work/SRC/openSUSE:Factory/.semaphore.new.1991 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "semaphore"

Tue Jul 14 13:51:43 2026 rev:52 rq:1365476 version:2.18.26

Changes:
--------
--- /work/SRC/openSUSE:Factory/semaphore/semaphore.changes      2026-07-10 
17:45:55.442630899 +0200
+++ /work/SRC/openSUSE:Factory/.semaphore.new.1991/semaphore.changes    
2026-07-14 13:52:07.795517240 +0200
@@ -1,0 +2,6 @@
+Tue Jul 14 05:34:28 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- Update to version 2.18.26:
+  * feat(auth): verify current password to fix CWE-620
+
+-------------------------------------------------------------------

Old:
----
  semaphore-2.18.25.obscpio
  web-2.18.25.tar.gz

New:
----
  semaphore-2.18.26.obscpio
  web-2.18.26.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ semaphore.spec ++++++
--- /var/tmp/diff_new_pack.hJA4Da/_old  2026-07-14 13:52:10.947626226 +0200
+++ /var/tmp/diff_new_pack.hJA4Da/_new  2026-07-14 13:52:10.947626226 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           semaphore
-Version:        2.18.25
+Version:        2.18.26
 Release:        0
 Summary:        Modern UI for Ansible
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.hJA4Da/_old  2026-07-14 13:52:11.055629961 +0200
+++ /var/tmp/diff_new_pack.hJA4Da/_new  2026-07-14 13:52:11.059630099 +0200
@@ -3,7 +3,7 @@
     <param 
name="url">https://github.com/ansible-semaphore/semaphore.git</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">refs/tags/v2.18.25</param>
+    <param name="revision">refs/tags/v2.18.26</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.hJA4Da/_old  2026-07-14 13:52:11.095631343 +0200
+++ /var/tmp/diff_new_pack.hJA4Da/_new  2026-07-14 13:52:11.107631758 +0200
@@ -3,6 +3,6 @@
                 <param 
name="url">https://github.com/ansible-semaphore/semaphore</param>
               <param 
name="changesrevision">8a4dcf0868af718aaa5871368a3247dd622521f4</param></service><service
 name="tar_scm">
                 <param 
name="url">https://github.com/ansible-semaphore/semaphore.git</param>
-              <param 
name="changesrevision">7c3789c890d7e92fb8ca2a63ad74babf460a13fa</param></service></servicedata>
+              <param 
name="changesrevision">ee781a032ccb58c61de6b9a70993f9b6dc77dcf8</param></service></servicedata>
 (No newline at EOF)
 

++++++ semaphore-2.18.25.obscpio -> semaphore-2.18.26.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.25/.vscode/launch.json 
new/semaphore-2.18.26/.vscode/launch.json
--- old/semaphore-2.18.25/.vscode/launch.json   2026-07-09 13:57:28.000000000 
+0200
+++ new/semaphore-2.18.26/.vscode/launch.json   2026-07-12 13:37:09.000000000 
+0200
@@ -55,7 +55,7 @@
             "request": "launch",
             "mode": "auto",
             "program": "./cli/main.go",
-            "args": ["server", "--config", "./config.json"],
+            "args": ["server", "--config", "./config.yaml"],
             "cwd": "${workspaceFolder}",
             "env": {
                 "DEBUG_DELAY": "1s"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.25/api/users.go 
new/semaphore-2.18.26/api/users.go
--- old/semaphore-2.18.25/api/users.go  2026-07-09 13:57:28.000000000 +0200
+++ new/semaphore-2.18.26/api/users.go  2026-07-12 13:37:09.000000000 +0200
@@ -12,6 +12,7 @@
        "github.com/semaphoreui/semaphore/db"
        "github.com/semaphoreui/semaphore/pro_interfaces"
        log "github.com/sirupsen/logrus"
+       "golang.org/x/crypto/bcrypt"
 
        "github.com/semaphoreui/semaphore/util"
 )
@@ -244,7 +245,8 @@
        editor := helpers.GetFromContext(r, "user").(*db.User)
 
        var pwd struct {
-               Pwd string `json:"password"`
+               Pwd        string `json:"password"`
+               CurrentPwd string `json:"current_password"`
        }
 
        if !editor.Admin && editor.ID != user.ID {
@@ -266,6 +268,17 @@
                return
        }
 
+       // CWE-620: require the current password when a user changes their own,
+       // so a stolen session can't be used to take over the account. Admins
+       // changing someone else's password are exempt — they can't know it.
+       if editor.ID == user.ID {
+               if err := bcrypt.CompareHashAndPassword([]byte(user.Password), 
[]byte(pwd.CurrentPwd)); err != nil {
+                       c.log.WithField("user_id", user.ID).Debug("Current 
password does not match")
+                       helpers.WriteErrorStatus(w, "Current password is 
incorrect", http.StatusBadRequest)
+                       return
+               }
+       }
+
        if err := helpers.Store(r).SetUserPassword(user.ID, pwd.Pwd); err != 
nil {
                c.log.WithError(err).WithField("user_id", 
user.ID).Error("Failed to set user password")
                w.WriteHeader(http.StatusInternalServerError)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.25/api/users_test.go 
new/semaphore-2.18.26/api/users_test.go
--- old/semaphore-2.18.25/api/users_test.go     1970-01-01 01:00:00.000000000 
+0100
+++ new/semaphore-2.18.26/api/users_test.go     2026-07-12 13:37:09.000000000 
+0200
@@ -0,0 +1,62 @@
+package api
+
+import (
+       "bytes"
+       "net/http"
+       "net/http/httptest"
+       "testing"
+
+       "github.com/semaphoreui/semaphore/api/helpers"
+       "github.com/semaphoreui/semaphore/db"
+       "github.com/semaphoreui/semaphore/db/sql"
+       "github.com/stretchr/testify/assert"
+)
+
+func newPasswordRequest(store db.Store, editor *db.User, target db.User, body 
string) *http.Request {
+       r := httptest.NewRequest(http.MethodPost, "/api/users/1/password", 
bytes.NewBufferString(body))
+       r = helpers.SetContextValue(r, "store", store)
+       r = helpers.SetContextValue(r, "user", editor)
+       r = helpers.SetContextValue(r, "_user", target)
+       return r
+}
+
+func TestUpdateUserPassword_SelfRequiresCurrentPassword(t *testing.T) {
+       store := sql.CreateTestStore()
+       user := createUserOptionsTestUser(t, store, "self") // password: 
verystrongpassword1
+
+       t.Run("correct current password", func(t *testing.T) {
+               r := newPasswordRequest(store, &user, user,
+                       
`{"current_password":"verystrongpassword1","password":"newpassword2"}`)
+               w := httptest.NewRecorder()
+               NewUsersController(nil).UpdateUserPassword(w, r)
+               assert.Equal(t, http.StatusNoContent, w.Code)
+       })
+
+       t.Run("wrong current password is rejected", func(t *testing.T) {
+               r := newPasswordRequest(store, &user, user,
+                       
`{"current_password":"wrong","password":"newpassword3"}`)
+               w := httptest.NewRecorder()
+               NewUsersController(nil).UpdateUserPassword(w, r)
+               assert.Equal(t, http.StatusBadRequest, w.Code)
+       })
+
+       t.Run("missing current password is rejected", func(t *testing.T) {
+               r := newPasswordRequest(store, &user, user, 
`{"password":"newpassword4"}`)
+               w := httptest.NewRecorder()
+               NewUsersController(nil).UpdateUserPassword(w, r)
+               assert.Equal(t, http.StatusBadRequest, w.Code)
+       })
+}
+
+func TestUpdateUserPassword_AdminExemptForOtherUsers(t *testing.T) {
+       store := sql.CreateTestStore()
+       admin := createUserOptionsTestUser(t, store, "admin")
+       admin.Admin = true
+       target := createUserOptionsTestUser(t, store, "target")
+
+       // Admin changing someone else's password does not need the current one.
+       r := newPasswordRequest(store, &admin, target, 
`{"password":"resetbyanadmin1"}`)
+       w := httptest.NewRecorder()
+       NewUsersController(nil).UpdateUserPassword(w, r)
+       assert.Equal(t, http.StatusNoContent, w.Code)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/semaphore-2.18.25/web/src/components/ChangePasswordForm.vue 
new/semaphore-2.18.26/web/src/components/ChangePasswordForm.vue
--- old/semaphore-2.18.25/web/src/components/ChangePasswordForm.vue     
2026-07-09 13:57:28.000000000 +0200
+++ new/semaphore-2.18.26/web/src/components/ChangePasswordForm.vue     
2026-07-12 13:37:09.000000000 +0200
@@ -1,14 +1,20 @@
 <template>
-  <v-form
-    ref="form"
-    lazy-validation
-    v-model="formValid"
-  >
-    <v-alert
-      :value="formError"
-      color="error"
-      class="pb-2"
-    >{{ formError }}</v-alert>
+  <v-form ref="form" lazy-validation v-model="formValid">
+    <v-alert :value="formError" color="error" class="pb-2">{{ formError 
}}</v-alert>
+
+    <v-text-field
+      v-if="isSelf"
+      v-model="item.current_password"
+      :label="$t('currentPassword')"
+      :class="{ 'masked-secret-input': !showCurrentPassword }"
+      :append-icon="showCurrentPassword ? 'mdi-eye' : 'mdi-eye-off'"
+      @click:append="showCurrentPassword = !showCurrentPassword"
+      :rules="[(v) => !!v || $t('password_required')]"
+      required
+      dense
+      outlined
+      :disabled="formSaving"
+    ></v-text-field>
 
     <v-text-field
       v-model="item.password"
@@ -16,13 +22,16 @@
       :class="{ 'masked-secret-input': !showPassword }"
       :append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
       @click:append="showPassword = !showPassword"
-      :rules="[v => !!v || $t('password_required')]"
+      :rules="[(v) => !!v || $t('password_required')]"
       required
+      dense
+      outlined
       :disabled="formSaving"
     ></v-text-field>
   </v-form>
 </template>
 <script>
+import axios from 'axios';
 import ItemFormBase from '@/components/ItemFormBase';
 
 export default {
@@ -31,12 +40,17 @@
   data() {
     return {
       showPassword: false,
+      showCurrentPassword: false,
+      isSelf: false,
     };
   },
 
   methods: {
     async loadData() {
       this.item = {};
+      // Require the current password only when changing your own (CWE-620).
+      const currentUser = (await axios.get('/api/user')).data;
+      this.isSelf = currentUser.id === Number(this.itemId);
     },
 
     getItemsUrl() {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.25/web/src/lang/en.js 
new/semaphore-2.18.26/web/src/lang/en.js
--- old/semaphore-2.18.25/web/src/lang/en.js    2026-07-09 13:57:28.000000000 
+0200
+++ new/semaphore-2.18.26/web/src/lang/en.js    2026-07-12 13:37:09.000000000 
+0200
@@ -73,6 +73,7 @@
   semaphore: 'SEMAPHORE',
   dontHaveAccountOrCantSignIn: "Don't have account or can't sign in?",
   password2: 'Password',
+  currentPassword: 'Current password',
   cancel: 'Cancel',
   noViews: 'No views',
   addView: 'Add view',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/semaphore-2.18.25/web/src/views/Users.vue 
new/semaphore-2.18.26/web/src/views/Users.vue
--- old/semaphore-2.18.25/web/src/views/Users.vue       2026-07-09 
13:57:28.000000000 +0200
+++ new/semaphore-2.18.26/web/src/views/Users.vue       2026-07-12 
13:37:09.000000000 +0200
@@ -19,6 +19,7 @@
           @hide-action-buttons="hideEditDialogButtons = true"
           @show-action-buttons="hideEditDialogButtons = false"
           :auth-methods="authMethods"
+          :login-with-password="(systemInfo || {}).login_with_password"
         />
       </template>
     </EditDialog>
@@ -30,20 +31,13 @@
       @yes="deleteItem(itemId)"
     />
 
-    <v-toolbar flat >
-      <v-btn
-        icon
-        class="mr-4"
-        @click="returnToProjects()"
-      >
+    <v-toolbar flat>
+      <v-btn icon class="mr-4" @click="returnToProjects()">
         <v-icon>mdi-arrow-left</v-icon>
       </v-btn>
       <v-toolbar-title>{{ $t('users') }}</v-toolbar-title>
       <v-spacer></v-spacer>
-      <v-btn
-        color="primary"
-        @click="editItem('new')"
-      >{{ $t('newUser') }}</v-btn>
+      <v-btn color="primary" @click="editItem('new')">{{ $t('newUser') 
}}</v-btn>
     </v-toolbar>
 
     <v-divider />
@@ -71,19 +65,11 @@
 
       <template v-slot:item.actions="{ item }">
         <div style="white-space: nowrap">
-          <v-btn
-            icon
-            class="mr-1"
-            @click="askDeleteItem(item.id)"
-          >
+          <v-btn icon class="mr-1" @click="askDeleteItem(item.id)">
             <v-icon>mdi-delete</v-icon>
           </v-btn>
 
-          <v-btn
-            icon
-            class="mr-1"
-            @click="editItem(item.id)"
-          >
+          <v-btn icon class="mr-1" @click="editItem(item.id)">
             <v-icon>mdi-pencil</v-icon>
           </v-btn>
         </div>
@@ -103,6 +89,7 @@
 
   props: {
     authMethods: Object,
+    systemInfo: Object,
   },
 
   components: {
@@ -119,37 +106,39 @@
 
   methods: {
     getHeaders() {
-      return [{
-        text: this.$i18n.t('name'),
-        value: 'name',
-        width: '50%',
-      },
-      {
-        text: this.$i18n.t('username'),
-        value: 'username',
-      },
-      {
-        text: this.$i18n.t('email'),
-        value: 'email',
-      },
-      {
-        text: this.$i18n.t('alert'),
-        value: 'alert',
-      },
-      {
-        text: this.$i18n.t('admin'),
-        value: 'admin',
-      },
-      {
-        text: this.$i18n.t('external'),
-        value: 'external',
-        width: '50%',
-      },
-      {
-        text: this.$i18n.t('actions'),
-        value: 'actions',
-        sortable: false,
-      }];
+      return [
+        {
+          text: this.$i18n.t('name'),
+          value: 'name',
+          width: '50%',
+        },
+        {
+          text: this.$i18n.t('username'),
+          value: 'username',
+        },
+        {
+          text: this.$i18n.t('email'),
+          value: 'email',
+        },
+        {
+          text: this.$i18n.t('alert'),
+          value: 'alert',
+        },
+        {
+          text: this.$i18n.t('admin'),
+          value: 'admin',
+        },
+        {
+          text: this.$i18n.t('external'),
+          value: 'external',
+          width: '50%',
+        },
+        {
+          text: this.$i18n.t('actions'),
+          value: 'actions',
+          sortable: false,
+        },
+      ];
     },
 
     async returnToProjects() {

++++++ semaphore.obsinfo ++++++
--- /var/tmp/diff_new_pack.hJA4Da/_old  2026-07-14 13:52:12.783689709 +0200
+++ /var/tmp/diff_new_pack.hJA4Da/_new  2026-07-14 13:52:12.787689847 +0200
@@ -1,5 +1,5 @@
 name: semaphore
-version: 2.18.25
-mtime: 1783598248
-commit: 7c3789c890d7e92fb8ca2a63ad74babf460a13fa
+version: 2.18.26
+mtime: 1783856229
+commit: ee781a032ccb58c61de6b9a70993f9b6dc77dcf8
 

++++++ vendor.tar.gz ++++++
/work/SRC/openSUSE:Factory/semaphore/vendor.tar.gz 
/work/SRC/openSUSE:Factory/.semaphore.new.1991/vendor.tar.gz differ: char 151, 
line 2

++++++ web-2.18.25.tar.gz -> web-2.18.26.tar.gz ++++++
/work/SRC/openSUSE:Factory/semaphore/web-2.18.25.tar.gz 
/work/SRC/openSUSE:Factory/.semaphore.new.1991/web-2.18.26.tar.gz differ: char 
31, line 1

Reply via email to