#
SET(LIBZYPP_MAJOR "15")
SET(LIBZYPP_COMPATMINOR "19")
-SET(LIBZYPP_MINOR "21")
-SET(LIBZYPP_PATCH "7")
+SET(LIBZYPP_MINOR "22")
+SET(LIBZYPP_PATCH "0")
#
-# LAST RELEASED: 15.21.7 (19)
+# LAST RELEASED: 15.22.0 (19)
# (The number in parenthesis is LIBZYPP_COMPATMINOR)
#=======
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/zypp/systemCheck.d
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/zypp/vendors.d
mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/zypp/multiversion.d
+mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/zypp/credentials.d
mkdir -p $RPM_BUILD_ROOT%{_prefix}/lib/zypp
mkdir -p $RPM_BUILD_ROOT%{_prefix}/lib/zypp/plugins
mkdir -p $RPM_BUILD_ROOT%{_prefix}/lib/zypp/plugins/appdata
%dir %{_sysconfdir}/zypp/systemCheck.d
%dir %{_sysconfdir}/zypp/vendors.d
%dir %{_sysconfdir}/zypp/multiversion.d
+%dir %{_sysconfdir}/zypp/credentials.d
%config(noreplace) %{_sysconfdir}/zypp/zypp.conf
%config(noreplace) %{_sysconfdir}/zypp/systemCheck
%config(noreplace) %{_sysconfdir}/logrotate.d/zypp-history.lr
-------------------------------------------------------------------
+Mon Apr 25 14:59:41 CEST 2016 - ma@suse.de
+
+- Fix credential file parser losing entries with known URL but
+ different user name (bsc#933760)
+- RepoManager: allow extraction of multiple baseurls for service
+ repos (bsc#964932)
+- addRepository: fix to use the correct history file for logging
+- specfile: add /etc/zypp/credentials.d to the file list
+- version 15.22.0 (19)
+
+-------------------------------------------------------------------
Mon Apr 18 15:03:13 CEST 2016 - ma@suse.de
- RepoindexFileReader: fix service metadata TTL default value (bsc#967828)
_pimpl->baseUrls().raw().push_back( url_r );
}
+ void RepoInfo::setBaseUrls( url_set urls )
+ { _pimpl->baseUrls().raw().swap( urls ); }
+
void RepoInfo::setPath( const Pathname &path )
{ _pimpl->path = path; }
* Clears current base URL list and adds \a url.
*/
void setBaseUrl( const Url &url );
+ /**
+ * Clears current base URL list and adds an \ref url_set.
+ */
+ void setBaseUrls( url_set urls );
/**
* \short Repository path
///////////////////////////////////////////////////////////////////
namespace
{
+ ///////////////////////////////////////////////////////////////////
+ /// \class UrlCredentialExtractor
+ /// \brief Extract credentials in \ref Url authority and store them via \ref CredentialManager.
+ ///
+ /// Lazy init CredentialManager and save collected credentials when
+ /// going out of scope.
+ ///
+ /// Methods return whether a password has been collected/extracted.
+ ///
+ /// \code
+ /// UrlCredentialExtractor( "/rootdir" ).collect( oneUrlOrUrlContainer );
+ /// \endcode
+ /// \code
+ /// {
+ /// UrlCredentialExtractor extractCredentials;
+ /// extractCredentials.collect( oneUrlOrUrlContainer );
+ /// extractCredentials.extract( oneMoreUrlOrUrlContainer );
+ /// ....
+ /// }
+ /// \endcode
+ ///
+ class UrlCredentialExtractor
+ {
+ public:
+ UrlCredentialExtractor( Pathname & root_r )
+ : _root( root_r )
+ {}
+
+ ~UrlCredentialExtractor()
+ { if ( _cmPtr ) _cmPtr->save(); }
+
+ /** Remember credentials stored in URL authority leaving the password in \a url_r. */
+ bool collect( const Url & url_r )
+ {
+ bool ret = url_r.hasCredentialsInAuthority();
+ if ( ret )
+ {
+ if ( !_cmPtr ) _cmPtr.reset( new media::CredentialManager( _root ) );
+ _cmPtr->addUserCred( url_r );
+ }
+ return ret;
+ }
+ /** \overload operating on Url container */
+ template<class TContainer>
+ bool collect( const TContainer & urls_r )
+ { bool ret = false; for ( const Url & url : urls_r ) { if ( collect( url ) && !ret ) ret = true; } return ret; }
+
+ /** Remember credentials stored in URL authority stripping the passowrd from \a url_r. */
+ bool extract( Url & url_r )
+ {
+ bool ret = collect( url_r );
+ if ( ret )
+ url_r.setPassword( std::string() );
+ return ret;
+ }
+ /** \overload operating on Url container */
+ template<class TContainer>
+ bool extract( TContainer & urls_r )
+ { bool ret = false; for ( Url & url : urls_r ) { if ( extract( url ) && !ret ) ret = true; } return ret; }
+
+ private:
+ const Pathname & _root;
+ scoped_ptr<media::CredentialManager> _cmPtr;
+ };
+ } // namespace
+ ///////////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////
+ namespace
+ {
/** Simple media mounter to access non-downloading URLs e.g. for non-local plaindir repos.
* \ingroup g_RAII
*/
progress.set(90);
// check for credentials in Urls
- bool havePasswords = false;
- for_( urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd() )
- if ( urlit->hasCredentialsInAuthority() )
- {
- havePasswords = true;
- break;
- }
- // save the credentials
- if ( havePasswords )
- {
- media::CredentialManager cm(
- media::CredManagerOptions(_options.rootDir) );
-
- for_(urlit, tosave.baseUrlsBegin(), tosave.baseUrlsEnd())
- if (urlit->hasCredentialsInAuthority())
- //! \todo use a method calling UI callbacks to ask where to save creds?
- cm.saveInUser(media::AuthData(*urlit));
- }
+ UrlCredentialExtractor( _options.rootDir ).collect( tosave.baseUrls() );
- HistoryLog().addRepository(tosave);
+ HistoryLog(_options.rootDir).addRepository(tosave);
progress.toMax();
MIL << "done" << endl;
newinfo.setFilepath(toedit.filepath());
reposManip().erase(toedit);
reposManip().insert(newinfo);
+ // check for credentials in Urls
+ UrlCredentialExtractor( _options.rootDir ).collect( newinfo.baseUrls() );
HistoryLog(_options.rootDir).modifyRepository(toedit, newinfo);
MIL << "repo " << alias << " modified" << endl;
}
saveService( toSave );
_services.insert( toSave );
- // check for credentials in Url (username:password, not ?credentials param)
- if ( toSave.url().hasCredentialsInAuthority() )
- {
- media::CredentialManager cm(
- media::CredManagerOptions(_options.rootDir) );
-
- //! \todo use a method calling UI callbacks to ask where to save creds?
- cm.saveInUser(media::AuthData(toSave.url()));
- }
+ // check for credentials in Url
+ UrlCredentialExtractor( _options.rootDir ).collect( toSave.url() );
MIL << "added service " << toSave.alias() << endl;
}
{
// First of all: Prepend service alias:
it->setAlias( str::form( "%s:%s", service.alias().c_str(), it->alias().c_str() ) );
- // set refrence to the parent service
+ // set reference to the parent service
it->setService( service.alias() );
// remember the new parsed repo state
newRepoStates[it->alias()] = *it;
- // if the repo url was not set by the repoindex parser, set service's url
- Url url;
- if ( it->baseUrlsEmpty() )
- url = service.rawUrl();
- else
+ // - If the repo url was not set by the repoindex parser, set service's url.
+ // - Libzypp currently has problem with separate url + path handling so just
+ // append a path, if set, to the baseurls
+ // - Credentials in the url authority will be extracted later, either if the
+ // repository is added or if we check for changed urls.
+ Pathname path;
+ if ( !it->path().empty() )
{
- // service repo can contain only one URL now, so no need to iterate.
- url = it->rawUrl(); // raw!
+ if ( it->path() != "/" )
+ path = it->path();
+ it->setPath("");
}
- // libzypp currently has problem with separate url + path handling
- // so just append the path to the baseurl
- if ( !it->path().empty() )
+ if ( it->baseUrlsEmpty() )
{
- Pathname path(url.getPathName());
- path /= it->path();
- url.setPathName( path.asString() );
- it->setPath("");
+ Url url( service.rawUrl() );
+ if ( !path.empty() )
+ url.setPathName( url.getPathName() / path );
+ it->setBaseUrl( std::move(url) );
+ }
+ else if ( !path.empty() )
+ {
+ RepoInfo::url_set urls( it->rawBaseUrls() );
+ for ( Url & url : urls )
+ {
+ url.setPathName( url.getPathName() / path );
+ }
+ it->setBaseUrls( std::move(urls) );
}
-
- // save the url
- it->setBaseUrl( url );
}
////////////////////////////////////////////////////////////////////////////
}
////////////////////////////////////////////////////////////////////////////
- // create missing repositories and modify exising ones if needed...
+ // create missing repositories and modify existing ones if needed...
+ UrlCredentialExtractor urlCredentialExtractor( _options.rootDir ); // To collect any credentials stored in repo URLs
for_( it, collector.repos.begin(), collector.repos.end() )
{
// User explicitly requested the repo being enabled?
}
// changed url?
- // service repo can contain only one URL now, so no need to iterate.
- if ( oldRepo->rawUrl() != it->rawUrl() )
{
- DBG << "Service repo " << it->alias() << " gets new URL " << it->rawUrl() << endl;
- oldRepo->setBaseUrl( it->rawUrl() );
- oldRepoModified = true;
- }
+ RepoInfo::url_set newUrls( it->rawBaseUrls() );
+ urlCredentialExtractor.extract( newUrls ); // Extract! to prevent passwds from disturbing the comparison below
+ if ( oldRepo->rawBaseUrls() != newUrls )
+ {
+ DBG << "Service repo " << it->alias() << " gets new URLs " << newUrls << endl;
+ oldRepo->setBaseUrls( std::move(newUrls) );
+ oldRepoModified = true;
+ }
+ }
// changed gpg check settings?
// ATM only plugin services can set GPG values.
_services.erase(oldAlias);
_services.insert(service);
+ // check for credentials in Urls
+ UrlCredentialExtractor( _options.rootDir ).collect( service.url() );
+
// changed properties affecting also repositories
if ( oldAlias != service.alias() // changed alias
#undef ZYPP_BASE_LOGGER_LOGGROUP
#define ZYPP_BASE_LOGGER_LOGGROUP "parser"
-
///////////////////////////////////////////////////////////////////
namespace zypp
-{ /////////////////////////////////////////////////////////////////
+{
///////////////////////////////////////////////////////////////////
namespace media
- { /////////////////////////////////////////////////////////////////
-
-
- //////////////////////////////////////////////////////////////////////
- //
- // CLASS NAME : CredentialFileReader
- //
- //////////////////////////////////////////////////////////////////////
-
- CredentialFileReader::CredentialFileReader(
- const Pathname & crfile,
- const ProcessCredentials & callback)
{
- InputStream is(crfile);
- parser::IniDict dict(is);
- for (parser::IniDict::section_const_iterator its = dict.sectionsBegin();
- its != dict.sectionsEnd();
- ++its)
+ ///////////////////////////////////////////////////////////////////
+ namespace
{
- Url storedUrl;
- if (!its->empty())
- {
- try { storedUrl = Url(*its); }
- catch (const url::UrlException &)
- {
- ERR << "invalid URL '" << *its << "' in credentials in file: "
- << crfile << endl;
- continue;
- }
- }
-
- AuthData_Ptr credentials;
- credentials.reset(new AuthData());
-
- // set url
- if (storedUrl.isValid())
- credentials->setUrl(storedUrl);
-
- for (parser::IniDict::entry_const_iterator it = dict.entriesBegin(*its);
- it != dict.entriesEnd(*its);
- ++it)
+ // Looks like INI but allows multiple sections for the same URL
+ // but different user (in .cat files). So don't use an Ini
+ // Also support a global section without '[URL]' which is used
+ // in credential files.
+ // -------------------------------------
+ // username = emptyUSER
+ // password = emptyPASS
+ // -------------------------------------
+ // [http://server/tmp/sumafake222]
+ // username = USER
+ // password = PASS
+ //
+ // [http://server/tmp/sumafake222]
+ // username = USER2
+ // password = PASS
+ // -------------------------------------
+ struct CredentialFileReaderImpl : public parser::IniParser
{
- if (it->first == "username")
- credentials->setUsername(it->second);
- else if (it->first == "password")
- credentials->setPassword(it->second);
- else
- ERR << "Unknown attribute in [" << crfile << "]: "
- << it->second << " ignored" << endl;
- }
-
- if (credentials->valid())
- callback(credentials);
- else
- ERR << "invalid credentials in file: " << crfile << endl;
- } // sections
- }
-
-
- CredentialFileReader::~CredentialFileReader()
- {}
-
-
- /////////////////////////////////////////////////////////////////
- } // media
+ typedef CredentialFileReader::ProcessCredentials ProcessCredentials;
+
+ struct StopParsing {};
+
+ CredentialFileReaderImpl( const Pathname & input_r, const ProcessCredentials & callback_r )
+ : _input( input_r )
+ , _callback( callback_r )
+ {
+ try
+ {
+ parse( input_r );
+ }
+ catch ( StopParsing )
+ { /* NO error but consumer aborted parsing */ }
+ }
+
+ // NO-OP; new sections are opened in consume()
+ virtual void beginParse()
+ { /*EMPTY*/ }
+
+ // start a new section [url]
+ virtual void consume( const std::string & section_r )
+ {
+ endParse(); // close any open section
+ _secret.reset( new AuthData );
+ try
+ {
+ _secret->setUrl( Url(section_r) );
+ }
+ catch ( const url::UrlException & )
+ {
+ ERR << "Ignore invalid URL '" << section_r << "' in file " << _input << endl;
+ _secret.reset(); // ignore this section
+ }
+ }
+
+ virtual void consume( const std::string & section_r, const std::string & key_r, const std::string & value_r )
+ {
+ if ( !_secret && section_r.empty() )
+ _secret.reset( new AuthData ); // a initial global section without [URL]
+
+ if ( _secret )
+ {
+ if ( key_r == "username" )
+ _secret->setUsername( value_r );
+ else if ( key_r == "password" )
+ _secret->setPassword( value_r );
+ else
+ WAR << "Ignore unknown attribute '" << key_r << "=" << value_r << "' in file " << _input << endl;
+ }
+ // else: ignored section due to wrong URL
+ }
+
+ // send any valid pending section
+ virtual void endParse()
+ {
+ if ( _secret )
+ {
+ if ( _secret->valid() )
+ {
+ if ( !_callback( _secret ) )
+ throw( StopParsing() );
+ }
+ else
+ ERR << "Ignore invalid credentials for URL '" << _secret->url() << "' in file " << _input << endl;
+ }
+ }
+
+ private:
+ const Pathname & _input;
+ const ProcessCredentials & _callback;
+ AuthData_Ptr _secret;
+ };
+ } // namespace
+ ///////////////////////////////////////////////////////////////////
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // CLASS NAME : CredentialFileReader
+ //
+ //////////////////////////////////////////////////////////////////////
+
+ CredentialFileReader::CredentialFileReader( const Pathname & crfile_r, const ProcessCredentials & callback_r )
+ { CredentialFileReaderImpl( crfile_r, callback_r ); }
+
+ CredentialFileReader::~CredentialFileReader()
+ {}
+
+ } // namespace media
///////////////////////////////////////////////////////////////////
- /////////////////////////////////////////////////////////////////
-} // zypp
+} // namespace zypp
///////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
namespace zypp
-{ /////////////////////////////////////////////////////////////////
+{
///////////////////////////////////////////////////////////////////
namespace media
- { /////////////////////////////////////////////////////////////////
-
-
- //////////////////////////////////////////////////////////////////////
- //
- // CLASS NAME : CredentialFileReader
- //
- class CredentialFileReader
{
- public:
- /**
- * Callback definition.
- * First parameter is the \ref Url with which the credentials are
- * associated, the second are the credentials.
- *
- * Return false from the callback to get a \ref AbortRequestException
- * to be thrown and the processing to be cancelled.
- */
- typedef function<bool(AuthData_Ptr &)> ProcessCredentials;
-
- CredentialFileReader(const Pathname & crfile,
- const ProcessCredentials & callback);
- ~CredentialFileReader();
- private:
- ProcessCredentials _callback;
- };
- //////////////////////////////////////////////////////////////////////
-
-
- /////////////////////////////////////////////////////////////////
- } // media
+ //////////////////////////////////////////////////////////////////////
+ /// \class CredentialFileReader
+ /// \brief Parse credentials files and catalogs
+ class CredentialFileReader
+ {
+ public:
+ /** Callback invoked for each entry found in the file.
+ * Return \c false to abort parsing.
+ */
+ typedef function<bool(AuthData_Ptr &)> ProcessCredentials;
+
+ CredentialFileReader( const Pathname & crfile_r, const ProcessCredentials & callback_r );
+ ~CredentialFileReader();
+ private:
+ ProcessCredentials _callback;
+ };
+ //////////////////////////////////////////////////////////////////////
+
+ } // namespace media
///////////////////////////////////////////////////////////////////
- /////////////////////////////////////////////////////////////////
-} // zypp
+} // namespace zypp
///////////////////////////////////////////////////////////////////
#endif /* ZYPP_MEDIA_CREDENTIALFILEREADER_H */
//
//////////////////////////////////////////////////////////////////////
- bool
- AuthDataComparator::operator()(
- const AuthData_Ptr & lhs, const AuthData_Ptr & rhs)
+ bool AuthDataComparator::operator()( const AuthData_Ptr & lhs, const AuthData_Ptr & rhs )
{
- static const url::ViewOption vopt =
- url::ViewOption::DEFAULTS
- - url::ViewOption::WITH_USERNAME
- - url::ViewOption::WITH_PASSWORD
- - url::ViewOption::WITH_QUERY_STR;
-
- if (lhs->username() != rhs->username())
- return true;
-
- if (lhs->url().asString(vopt) != rhs->url().asString(vopt))
- return true;
-
- return false;
+ static const url::ViewOption vopt = url::ViewOption::DEFAULTS
+ - url::ViewOption::WITH_USERNAME
+ - url::ViewOption::WITH_PASSWORD
+ - url::ViewOption::WITH_QUERY_STR;
+ // std::less semantic!
+ int cmp = lhs->url().asString(vopt).compare( rhs->url().asString(vopt) );
+ if ( ! cmp )
+ cmp = lhs->username().compare( rhs->username() );
+ return( cmp < 0 );
}
//////////////////////////////////////////////////////////////////////
std::ostream & AuthData::dumpOn( std::ostream & str ) const
{
+ if (_url.isValid())
+ str << "[" << _url.asString( url::ViewOptions() - url::ViewOptions::WITH_USERNAME - url::ViewOptions::WITH_PASSWORD ) << "]" << endl;
+ else
+ str << "[<no-url>]" << endl;
str << "username: '" << _username << "'" << std::endl
- << "password: " << (_password.empty() ? "<empty>" : "<non-empty>")
- << std::endl;
+ << "password: " << (_password.empty() ? "<empty>" : "<non-empty>");
return str;
}
std::ostream & CurlAuthData::dumpOn( std::ostream & str ) const
{
- AuthData::dumpOn(str) << " auth_type: " << _auth_type_str
- << " (" << _auth_type << ")" << std::endl;
+ AuthData::dumpOn(str) << endl
+ << " auth_type: " << _auth_type_str << " (" << _auth_type << ")";
return str;
}